<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=2">
<meta name="theme-color" content="#222">
<meta name="generator" content="Hexo 6.3.0">
  <link rel="apple-touch-icon" sizes="180x180" href="/images/apple-touch-icon-next.png">
  <link rel="icon" type="image/png" sizes="32x32" href="/images/favicon-32x32-next.png">
  <link rel="icon" type="image/png" sizes="16x16" href="/images/favicon-16x16-next.png">
  <link rel="mask-icon" href="/images/logo.svg" color="#222">

<link rel="stylesheet" href="/css/main.css">


<link rel="stylesheet" href="/lib/font-awesome/css/all.min.css">

<script id="hexo-configurations">
    var NexT = window.NexT || {};
    var CONFIG = {"hostname":"chuangges.gitee.io","root":"/","scheme":"Pisces","version":"7.8.0","exturl":false,"sidebar":{"position":"right","display":"post","padding":18,"offset":12,"onmobile":false},"copycode":{"enable":true,"show_result":true,"style":"mac"},"back2top":{"enable":true,"sidebar":false,"scrollpercent":false},"bookmark":{"enable":false,"color":"#222","save":"auto"},"fancybox":false,"mediumzoom":false,"lazyload":false,"pangu":false,"comments":{"style":"tabs","active":null,"storage":true,"lazyload":false,"nav":null},"algolia":{"hits":{"per_page":10},"labels":{"input_placeholder":"Search for Posts","hits_empty":"We didn't find any results for the search: ${query}","hits_stats":"${hits} results found in ${time} ms"}},"localsearch":{"enable":false,"trigger":"auto","top_n_per_article":1,"unescape":false,"preload":false},"motion":{"enable":true,"async":false,"transition":{"post_block":"fadeIn","post_header":"slideDownIn","post_body":"slideDownIn","coll_header":"slideLeftIn","sidebar":"slideUpIn"}},"path":"search.xml"};
  </script>

  <meta name="description" content="微服务、微前端、中间件、NodeJS 微服务">
<meta property="og:type" content="article">
<meta property="og:title" content="微服务技术栈">
<meta property="og:url" content="https://chuangges.gitee.io/2022/11/10/18-%E5%BE%AE%E6%9C%8D%E5%8A%A1/index.html">
<meta property="og:site_name" content="闯哥要去流浪了">
<meta property="og:description" content="微服务、微前端、中间件、NodeJS 微服务">
<meta property="og:locale" content="zh_CN">
<meta property="og:image" content="https://chuangges.gitee.io/images/web/Microservice.png">
<meta property="og:image" content="https://chuangges.gitee.io/images/web/microfront.png">
<meta property="article:published_time" content="2022-11-10T03:48:03.000Z">
<meta property="article:modified_time" content="2023-11-04T09:35:49.565Z">
<meta property="article:author" content="chuangges">
<meta property="article:tag" content="前端技术栈">
<meta name="twitter:card" content="summary">
<meta name="twitter:image" content="https://chuangges.gitee.io/images/web/Microservice.png">

<link rel="canonical" href="https://chuangges.gitee.io/2022/11/10/18-%E5%BE%AE%E6%9C%8D%E5%8A%A1/">


<script id="page-configurations">
  // https://hexo.io/docs/variables.html
  CONFIG.page = {
    sidebar: "",
    isHome : false,
    isPost : true,
    lang   : 'zh-CN'
  };
</script>

  <title>微服务技术栈 | 闯哥要去流浪了</title>
  






  <noscript>
  <style>
  .use-motion .brand,
  .use-motion .menu-item,
  .sidebar-inner,
  .use-motion .post-block,
  .use-motion .pagination,
  .use-motion .comments,
  .use-motion .post-header,
  .use-motion .post-body,
  .use-motion .collection-header { opacity: initial; }

  .use-motion .site-title,
  .use-motion .site-subtitle {
    opacity: initial;
    top: initial;
  }

  .use-motion .logo-line-before i { left: initial; }
  .use-motion .logo-line-after i { right: initial; }
  </style>
</noscript>

<link rel="alternate" href="/atom.xml" title="闯哥要去流浪了" type="application/atom+xml">
</head>

<body itemscope itemtype="http://schema.org/WebPage">
  <div class="container use-motion">
    <div class="headband"></div>

    <header class="header" itemscope itemtype="http://schema.org/WPHeader">
      <div class="header-inner"><div class="site-brand-container">
  <div class="site-nav-toggle">
    <div class="toggle" aria-label="切换导航栏">
      <span class="toggle-line toggle-line-first"></span>
      <span class="toggle-line toggle-line-middle"></span>
      <span class="toggle-line toggle-line-last"></span>
    </div>
  </div>

  <div class="site-meta">

    <a href="/" class="brand" rel="start">
      <span class="logo-line-before"><i></i></span>
      <h1 class="site-title">闯哥要去流浪了</h1>
      <span class="logo-line-after"><i></i></span>
    </a>
      <p class="site-subtitle" itemprop="description">愿你岁月可回首，且以深情共白头</p>
  </div>

  <div class="site-nav-right">
    <div class="toggle popup-trigger">
    </div>
  </div>
</div>




<nav class="site-nav">
  <ul id="menu" class="main-menu menu">
        <li class="menu-item menu-item-home">

    <a href="/" rel="section"><i class="fa fa-home fa-fw"></i>首页</a>

  </li>
        <li class="menu-item menu-item-tags">

    <a href="/tags/" rel="section"><i class="fa fa-tags fa-fw"></i>标签</a>

  </li>
        <li class="menu-item menu-item-categories">

    <a href="/categories/" rel="section"><i class="fa fa-th fa-fw"></i>分类</a>

  </li>
        <li class="menu-item menu-item-archives">

    <a href="/archives/" rel="section"><i class="fa fa-archive fa-fw"></i>归档</a>

  </li>
  </ul>
</nav>




</div>
    </header>

    
  <div class="back-to-top">
    <i class="fa fa-arrow-up"></i>
    <span>0%</span>
  </div>
  <div class="reading-progress-bar"></div>


    <main class="main">
      <div class="main-inner">
        <div class="content-wrap">
          

          <div class="content post posts-expand">
            

    
  
  
  <article itemscope itemtype="http://schema.org/Article" class="post-block" lang="zh-CN">
    <link itemprop="mainEntityOfPage" href="https://chuangges.gitee.io/2022/11/10/18-%E5%BE%AE%E6%9C%8D%E5%8A%A1/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/images/chuang.png">
      <meta itemprop="name" content="chuangges">
      <meta itemprop="description" content="chuangge's blog.">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="闯哥要去流浪了">
    </span>
      <header class="post-header">
        <h1 class="post-title" itemprop="name headline">
          微服务技术栈
        </h1>

        <div class="post-meta">
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-calendar"></i>
              </span>
              <span class="post-meta-item-text">发表于</span>

              <time title="创建时间：2022-11-10 11:48:03" itemprop="dateCreated datePublished" datetime="2022-11-10T11:48:03+08:00">2022-11-10</time>
            </span>
              <span class="post-meta-item">
                <span class="post-meta-item-icon">
                  <i class="far fa-calendar-check"></i>
                </span>
                <span class="post-meta-item-text">更新于</span>
                <time title="修改时间：2023-11-04 17:35:49" itemprop="dateModified" datetime="2023-11-04T17:35:49+08:00">2023-11-04</time>
              </span>
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-folder"></i>
              </span>
              <span class="post-meta-item-text">分类于</span>
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/categories/%E5%89%8D%E7%AB%AF%E6%8A%80%E6%9C%AF%E6%A0%88/" itemprop="url" rel="index"><span itemprop="name">前端技术栈</span></a>
                </span>
            </span>

          <br>
            <span class="post-meta-item" title="本文字数">
              <span class="post-meta-item-icon">
                <i class="far fa-file-word"></i>
              </span>
                <span class="post-meta-item-text">本文字数：</span>
              <span>33k</span>
            </span>
            <span class="post-meta-item" title="阅读时长">
              <span class="post-meta-item-icon">
                <i class="far fa-clock"></i>
              </span>
                <span class="post-meta-item-text">阅读时长 &asymp;</span>
              <span>30 分钟</span>
            </span>
            <div class="post-description">微服务、微前端、中间件、NodeJS 微服务</div>

        </div>
      </header>

    
    
    
    <div class="post-body" itemprop="articleBody">

      
        <h1 id="一、微服务"><a href="#一、微服务" class="headerlink" title="一、微服务"></a>一、微服务</h1><h2 id="系统架构"><a href="#系统架构" class="headerlink" title="系统架构"></a>系统架构</h2><blockquote>
<p><code>分布式</code>通过服务拆分解耦的方式实现了性能优化和按需更新但不能解决并发性和高可用性问题，<code>集群</code>则解决了系统的并发问题还可以在服务器宕机时正常运转，但是集群通过一台主机接收请求然后分发请求到部署相同服务的不同服务器实现负载均衡，本质上还是一个<code>单体应用架构</code>。具体设计时一般会结合分布式和集群的特点，即对拆分的子模块进行集群部署。</p>
</blockquote>
<ul>
<li><strong>单体应用</strong>：所有模块部署在一个服务器。优点是开发成本低、小型项目的首选，缺点是复杂项目不易开发和维护、性能扩展只能通过扩展集群结点但是有瓶颈和较高成本。<code>集群</code>可以提升性能但也有用户访问哪个服务器和数据共享的问题，<code>Niginx</code> 可用来反向代理用户请求地址并负载均衡完成用户请求的转发，<code>Redis + Tomcat</code> 则是实现数据共享的集群方案，但是这样的高并发问题导致数据库的压力过大，<code>数据库优化方案</code>主要有主从复制、Redis 缓存等。</li>
<li><strong>垂直应用</strong>：前后端分离的 MVC 分层架构，比如<code>水平拆分</code>根据层次拆分为工具库、数据持久、业务逻辑、后台管理、web 访问等模块并根据访问量合理部署，<code>垂直拆分</code>则根据功能拆分为商品、支付、购物车等独立模块。优点是前后端可以采用不同的技术开发、减轻了服务器的压力，缺点是某个模块更新时需要重新部署整个应用、不同服务器部署的模块之间无法交互。</li>
<li><strong>分布式架构</strong>：根据业务拆分为多个独立模块并通过 <code>RPC、Http、HttpClient</code> 等底层技术解决模块之间的调用问题。但是当服务越来越多，容量评估、小服务浪费资源等问题逐渐显现，此时需增加一个调度中心 SOA 基于访问压力实时管理集群容量并提高集群利用率。</li>
<li><strong>SOA 面向服务的架构</strong>：一种以粗粒度(表示类别)、松耦合的服务为中心的架构。实现方式主要有<code>中心化的 ESB、去中心化的微服务</code>。ESB 总线通过协议转换、消息路由、事务控制等底层技术对不同技术栈开发的<code>异构系统</code>实现连接通信和统一管理，将所有服务集成为一个松耦合的分布式系统，但是这种<code>单体架构模式</code>也带来了环境搭建困难和性能瓶颈等问题。 </li>
<li><strong>微服务架构</strong>：基于 SOA 理念针对业务实现细粒度(表示对象类别的实例)的组件化和服务化，通过<code>RPC/REST</code>协议对松耦合的微服务实现管理和整合，ESB 则是为各自为政的微服务们创造了一个中心化的政府。这种架构支持跨语言开发和独立部署，解决了复杂应用的快速开发问题，但也存在跨服务更改、调用链追踪、配置管理等问题，适用于长期迭代更新的项目。</li>
</ul>
<h2 id="开发技术"><a href="#开发技术" class="headerlink" title="开发技术"></a>开发技术</h2><blockquote>
<p>单体应用拆分出的独立服务就是微服务，它们相互调用会形成一个中心的<code>服务集群</code>，不同中心则是<code>分布式</code>，为了统一管理和配置而有了<code>注册中心与配置中心</code>、用户访问服务时在服务集群中处理负载均衡和请求路由的 <code>API 网关</code>，服务之间的通信机制则基于 <code>对内的 REST、对外的 RPC、异步的消息队列</code>，为了数据库高并发查询数据时更安全快速而产生了<code>分布式缓存和分布式搜索</code>，为了在调用链某一环节出错时不影响整体而提出的方案有<code>重试机制、限流、熔断机制、负载均衡、降级</code>，为了排查服务故障而引入<code>分布式日志服务</code>来记录请求信息并通过<code>系统监控、链路追踪</code>来监控整个系统运行并在异常时快速反应，最后通过 <code>Jenkins + docker + kubernetes/rancher</code> 实现自动化部署。一个微服务项目的架构和主要技术栈如下：</p>
</blockquote>
  <div align="center">
      <img src="" data-original="/images/web/Microservice.png" alt="相关配置" width="100%">
  </div>

<ul>
<li><strong>架构方案</strong>：<code>聚合器</code>(将多个服务合并处理为一个新服务)、<code>代理</code>(服务转发和数据转换)、<code>链式</code>(同步调用的微服务链条)、<code>分步式</code>(支持同时调用多个微服务链)、<code>数据共享</code>(支持部分微服务共享数据库和缓存而存在强耦合)、<code>异步消息</code>(通过消息队列中间件实现异构系统的连接通信)。</li>
<li><strong>远程调用</strong>：同步消息通过基于 HTTP 对接第三方服务的接口规范 <code>REST</code>(对外暴露接口)、基于 TCP&#x2F;UDP 内部高效通信的远程调用协议 <code>RPC</code>(对内直接调用)，异步通信则通过<code>消息队列</code>。</li>
<li><strong>RPC 框架</strong>：Java 语言的 <code>Dubbo、Motan、SpringBoot、SpringCloud</code>，NodeJS 语言的 <code>Seneca、Hapi、Restify、LoopBack</code>，支持跨语言平台开发的 <code>gRPC、Thrift</code>。</li>
<li><strong>主要技术栈及其常用中间件</strong>：<code>配置管理</code> SpringCloudConfig Nacos、<code>注册发现</code> Nacos Eureka Zookeeper Consul、<code>API网关</code> SpringCloudGateway Zuul、<code>熔断限流</code> Hystrix Envoy Sentinel、<code>服务监控</code> Zabbix Nagios Metrics Specatator、<code>负载均衡</code> Ribbon Nginx、<code>消息队列</code> Kafka RabbitMQ ActiveMQ、<code>链路追踪</code> Zipkin Brave Dapper、<code>自动化部署</code> Jenkins Docker Rancher Kubernetes。</li>
</ul>
<h1 id="二、微前端"><a href="#二、微前端" class="headerlink" title="二、微前端"></a>二、微前端</h1><blockquote>
<p>微服务架构拆分出可以相互调用的微服务而实现<code>后端的依赖解耦</code>，微前端架构则是将 React 微应用、Vue 微应用、共享模块等<code>独立应用在项目构建时聚合</code>为一个单体应用，用于解决多仓库项目的依赖管理、版本控制、共享模块等痛点。架构设计时主要内容有组件库和公共方法、应用通信机制、数据共享机制、专用的构建系统 (依赖于构建工具创建的前端应用需要设计)。</p>
</blockquote>
<h2 id="架构设计"><a href="#架构设计" class="headerlink" title="架构设计"></a>架构设计</h2>  <div align="center">
      <img src="" data-original="/images/web/microfront.png" alt="微前端架构" width="100%">
  </div>

  <figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 1、Monolithic 单体应用：一个仓库承接一个应用，业务复杂时代吗构建效率低。</span></span><br><span class="line">project</span><br><span class="line">├── node_modules/</span><br><span class="line">│   ├── lib@<span class="number">1.0</span><span class="number">.0</span></span><br><span class="line">├── src/</span><br><span class="line">│   ├── compA</span><br><span class="line">│   ├── compB</span><br><span class="line">│   └── compC</span><br><span class="line">└── package.<span class="property">json</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@title</span> 2、MultiRepo 多仓多模块：Babel6 之前版本都是采用这种管理方式。</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> 优点：将项目拆分为多个独立开发和发布的模块，解耦后提高了构建效率。</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> 缺点：跨仓库代码难共享、依赖管理复杂 (底层模块升级要更新上层依赖)、</span></span><br><span class="line"><span class="comment"> * 每个仓库都需要安装依赖而占用大量内存、不同模块的构建策略和开发规范不统一。</span></span><br><span class="line"><span class="comment">**/</span></span><br><span class="line">project</span><br><span class="line">├── node_modules/</span><br><span class="line">│   ├── lib@<span class="number">1.0</span><span class="number">.0</span></span><br><span class="line">│   ├── lib@<span class="number">2.0</span><span class="number">.0</span></span><br><span class="line">│   ├── pkgA</span><br><span class="line">│   ├── pkgB</span><br><span class="line">│   └── ..</span><br><span class="line">├── src/</span><br><span class="line">└── package.<span class="property">json</span></span><br><span class="line"></span><br><span class="line">packageA</span><br><span class="line">├── node_modules/</span><br><span class="line">│   └── lib@<span class="number">1.0</span><span class="number">.0</span></span><br><span class="line">├── src/</span><br><span class="line">└── package.<span class="property">json</span></span><br><span class="line"></span><br><span class="line">packageB</span><br><span class="line">├── node_modules/</span><br><span class="line">│   └── lib@<span class="number">2.0</span><span class="number">.0</span></span><br><span class="line">├── src/</span><br><span class="line">└── package.<span class="property">json</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@title</span> 3、MonoRepo 目前主流的单仓多模块：vue-cli、create-react-app。</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> 优点：简化依赖管理、便于代码复用和项目迭代、统一的配置和脚手架工具。</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> 缺点：代码开发权限管理困难、所有依赖安装耗时长、业务复杂时打包耗时。</span></span><br><span class="line"><span class="comment"> * </span></span><br><span class="line"><span class="comment"> * <span class="doctag">@title</span> 主要问题</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> 依赖管理：相同依赖放到根目录、pnpm 取代 npm 实现按需安装和依赖缓存。</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> 构建效率：全量构建改为差异的增量构建，依次执行的串行构建改为并行构建。</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> 版本管理：Lerna 优化了多包工作流，解决了多包依赖和发布版本维护的问题。</span></span><br><span class="line"><span class="comment"> * </span></span><br><span class="line"><span class="comment"> * <span class="doctag">@title</span> 技术方案</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> Lerna、Yarn、Pnpm：轻量的渐进式方案，主要解决管理依赖和版本的问题。</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> Turborepo、Rush、Nx：解决复杂项目构建效率低时性能优化的构建型方案。</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> Qiankun、MicroApp、EMP：直接使用提供了完整工作流和功能的主流框架。</span></span><br><span class="line"><span class="comment">**/</span></span><br><span class="line">vue3-project</span><br><span class="line">├── package.<span class="property">json</span></span><br><span class="line">├── tsconfig.<span class="property">json</span></span><br><span class="line">├── scripts</span><br><span class="line">├── node_modules</span><br><span class="line">├── node_modules</span><br><span class="line">└── packages            <span class="comment">// 基于 RollUp 构建的核心源码       </span></span><br><span class="line">    ├── compiler-dom       <span class="comment">// dom 编译器</span></span><br><span class="line">    ├── compiler-core      <span class="comment">// 核心编译器</span></span><br><span class="line">    ├── compiler-sfc       <span class="comment">// vue单文件编译器</span></span><br><span class="line">    ├── compiler-ssr       <span class="comment">// 服务端渲染编译</span></span><br><span class="line">    ├── <span class="variable language_">global</span>.<span class="property">d</span>.<span class="property">ts</span>        <span class="comment">// typescript声明文件</span></span><br><span class="line">    ├── reactivity         <span class="comment">// 响应式模块，可以与任何框架配合使用</span></span><br><span class="line">    ├── runtime-core       <span class="comment">// 运行时核心实例相关代码（平台无关）</span></span><br><span class="line">    ├── runtime-dom        <span class="comment">// 运行时dom 关api，属性，事件处理</span></span><br><span class="line">    ├── runtime-test       <span class="comment">// 运行时测试相关代码</span></span><br><span class="line">    ├── server-renderer    <span class="comment">// 服务端渲染</span></span><br><span class="line">    ├── sfc-playground     <span class="comment">// 单文件组件在线调试器</span></span><br><span class="line">    ├── shared             <span class="comment">// 内部工具库,不对外暴露API</span></span><br><span class="line">    ├── size-check         <span class="comment">// 简单应用，用来测试代码体积</span></span><br><span class="line">    ├── template-explorer  <span class="comment">// 用于调试编译器输出的开发工具</span></span><br><span class="line">    └── vue                <span class="comment">// 面向公众的完整版本，包含运行时和编译器</span></span><br></pre></td></tr></table></figure>
<h2 id="开发技术-1"><a href="#开发技术-1" class="headerlink" title="开发技术"></a>开发技术</h2><ul>
<li><strong>实现方案</strong>：将多个微应用聚合为一个单体应用的方式主要分为通过浏览器或服务器。</li>
<li><strong>Nginx 路由分发</strong>：服务端通过反向代理转发微应用路由，切换时会触发浏览器刷新。</li>
<li><strong>Iframe 沙箱隔离</strong>：主应用通过 iframe 加载的子应用具有天然的沙箱机制，但缺点主要有不支持 SEO、不共享变量、子应用切换重新加载而丢失状态、容易页面卡顿。</li>
<li><strong>WebCom 组件化</strong>：通过自定义元素来加载不同微应用，通过附加到元素来实现功能而且不会暴露到全局的 ShadowDOM 实现隔离，存在改造成本大、组件通信、浏览器兼容等问题。</li>
<li><strong>Webpack5 Module Federation 模块联邦</strong>：实现跨应用共享模块的新增功能，每个微应用都可以通过远程调用的方式引入共享模块。优点是去中心化的调用、子应用的依赖解耦，缺点是没有沙箱隔离(需借助工具)、代码封闭性高(需通过 npm 管理)、依赖前置而加长加载时间。</li>
<li><strong>中心基座 微应用化</strong>：主应用负责全局资源的加载运行、微应用的集成管理、路由映射、消息管理等，微应用独立开发并注册到基座。优点是支持 SEO、无感切换，缺点是沙箱不隔离。</li>
</ul>
<hr>
<ul>
<li><strong>开发工具</strong>：<code>Turborepo、Rush、Nx 等构建工具</code>可解决构建效率低的问题，针对初始&#x2F;构建&#x2F;单元测试&#x2F;集成测试等工作流随着代码增多而变慢的场景进行优化，<code>Lerna、Yarn、pnpm 等轻量型工具</code>则主要解决依赖管理和版本管理等问题，是一种初期建议选择的渐进式方案。我们也可通过引用下面的框架来简化开发流程，后台管理系统可选择 <code>Qiankun/MicroApp/Garfish</code>，老旧框架的项目可选择 <code>Qiankun/MicroApp</code>，SEO 项目可选择 <code>Piral/PuzzleJs</code>。</li>
<li><a target="_blank" rel="external nofollow noopener noreferrer" href="https://github.com/single-spa/single-spa">Single-spa</a>：将多个单页面应用聚合为一个整体应用的最早框架，可通过多种技术栈开发应用并使用公共路由实现切换，缺点有无通信机制、不支持 JS 沙箱、样式冲突、无法预加载。</li>
<li><a target="_blank" rel="external nofollow noopener noreferrer" href="https://github.com/umijs/qiankun">Qiankun</a>：阿里系开源的的 single-spa 优化版，基于中心基座方案实现但弥补了上述缺点。</li>
<li><a target="_blank" rel="external nofollow noopener noreferrer" href="https://zeroing.jd.com/micro-app/">MicroApp</a>：京东一款基于 WebComponent 实现的开源框架，接入成本最低并且功能完善。</li>
<li><a target="_blank" rel="external nofollow noopener noreferrer" href="https://github.com/efoxTeam/emp">EMP</a>：欢聚时代基于 Webpack5 Module Federation，支持跨应用状态共享、远程调用等。</li>
<li><a target="_blank" rel="external nofollow noopener noreferrer" href="https://github.com/modern-js-dev/garfish">Garfish</a>：字节跳动开源，主要解决跨团队协作、技术体系多样化、应用日益复杂化等问题。</li>
<li><a target="_blank" rel="external nofollow noopener noreferrer" href="https://github.com/Tencent/wujie">Wujie</a>：腾讯基于 WebComponent + iframe，极大降低了主应用使用和微应用适配的成本。</li>
<li><a target="_blank" rel="external nofollow noopener noreferrer" href="https://github.com/ice-lab/icestark">Icestark</a>：阿里面向大型系统，主应用管理微应用的注册和渲染而实现了系统的彻底解耦。</li>
</ul>
  <figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@title</span> JS 沙箱隔离：变量隔离、共享对象、多实例并行、加载卸载。</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> Iframe 沙箱：创建一个与主应用隔离的内部 Window 环境。</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> Proxy 沙箱：基于代理创建一些和其它应用共享的全局对象。</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> Diff 沙箱：在不支持代理的低版本浏览器用于取代 Proxy。</span></span><br><span class="line"><span class="comment">**/</span></span><br><span class="line"><span class="keyword">let</span> iframe  = <span class="variable language_">document</span>.<span class="title function_">createElement</span>(<span class="string">&#x27;iframe&#x27;</span>,&#123;<span class="attr">src</span>:<span class="string">&#x27;about:blank&#x27;</span>&#125;);</span><br><span class="line"><span class="variable language_">document</span>.<span class="property">body</span>.<span class="title function_">appendChild</span>(iframe);</span><br><span class="line"><span class="keyword">const</span> sandboxGlobal = iframe.<span class="property">contentWindow</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">SandboxWindow</span> &#123;</span><br><span class="line">    <span class="title function_">constructor</span>(<span class="params">context, frameWindow</span>) &#123;   </span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Proxy</span>(frameWindow, &#123;</span><br><span class="line">            <span class="title function_">get</span>(<span class="params">target, name</span>) &#123;</span><br><span class="line">                <span class="keyword">if</span> (name <span class="keyword">in</span> context) &#123; </span><br><span class="line">                    <span class="keyword">return</span> context[name];</span><br><span class="line">                &#125;</span><br><span class="line">                <span class="keyword">return</span> target[name];</span><br><span class="line">            &#125;,</span><br><span class="line">            <span class="title function_">set</span>(<span class="params">target, name, value</span>) &#123;</span><br><span class="line">                <span class="keyword">if</span> (name <span class="keyword">in</span> context) &#123; </span><br><span class="line">                    <span class="keyword">return</span> context[name] = value;</span><br><span class="line">                &#125;</span><br><span class="line">                target[name] = value;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;)</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// Proxy 沙箱实例：需要全局共享的对象、创建沙箱、判断沙箱上的对象和全局对象是否相等</span></span><br><span class="line"><span class="keyword">const</span> context = &#123; <span class="attr">document</span>: <span class="variable language_">window</span>.<span class="property">document</span>, <span class="attr">history</span>: <span class="variable language_">window</span>.<span class="property">history</span> &#125;</span><br><span class="line"><span class="keyword">const</span> newSandboxWindow = <span class="keyword">new</span> <span class="title class_">SandboxWindow</span>(context, sandboxGlobal)</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;equal&#x27;</span>, newSandboxWindow.<span class="property">document</span> === <span class="variable language_">window</span>.<span class="property">document</span>)</span><br><span class="line">newSandboxWindow.<span class="property">abc</span> = <span class="string">&#x27;1&#x27;</span>;           <span class="comment">// 在沙箱上添加属性</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="variable language_">window</span>.<span class="property">abc</span>);              <span class="comment">// 在全局上查看属性</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(newSandboxWindow.<span class="property">abc</span>)     <span class="comment">// 在沙箱上查看属性</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">DiffSandbox</span> &#123;</span><br><span class="line">    <span class="title function_">constructor</span>(<span class="params">name</span>) &#123;</span><br><span class="line">        <span class="variable language_">this</span>.<span class="property">name</span> = name;</span><br><span class="line">        <span class="variable language_">this</span>.<span class="property">modifyMap</span> = &#123;&#125;; </span><br><span class="line">        <span class="variable language_">this</span>.<span class="property">windowSnapshot</span> = &#123;&#125;;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="title function_">active</span>(<span class="params"></span>) &#123;</span><br><span class="line">        <span class="variable language_">this</span>.<span class="property">windowSnapshot</span> = &#123;&#125;;</span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">const</span> item <span class="keyword">in</span> <span class="variable language_">window</span>) &#123;</span><br><span class="line">            <span class="variable language_">this</span>.<span class="property">windowSnapshot</span>[item] = <span class="variable language_">window</span>[item];</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="title class_">Object</span>.<span class="title function_">keys</span>(<span class="variable language_">this</span>.<span class="property">modifyMap</span>).<span class="title function_">forEach</span>(<span class="function"><span class="params">p</span> =&gt;</span> &#123;</span><br><span class="line">            <span class="variable language_">window</span>[p] = <span class="variable language_">this</span>.<span class="property">modifyMap</span>[p];</span><br><span class="line">        &#125;)</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="title function_">inactive</span>(<span class="params"></span>) &#123;</span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">const</span> item <span class="keyword">in</span> <span class="variable language_">window</span>) &#123;</span><br><span class="line">            <span class="keyword">if</span> (<span class="variable language_">this</span>.<span class="property">windowSnapshot</span>[item] !== <span class="variable language_">window</span>[item]) &#123;</span><br><span class="line">                <span class="variable language_">this</span>.<span class="property">modifyMap</span>[item] = <span class="variable language_">window</span>[item];</span><br><span class="line">                <span class="variable language_">window</span>[item] = <span class="variable language_">this</span>.<span class="property">windowSnapshot</span>[item];</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">const</span> diffSandbox = <span class="keyword">new</span> <span class="title class_">DiffSandbox</span>(<span class="string">&#x27;diff沙箱&#x27;</span>);</span><br><span class="line">diffSandbox.<span class="title function_">active</span>();</span><br><span class="line"><span class="variable language_">window</span>.<span class="property">a</span> = <span class="string">&#x27;1&#x27;</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;开启沙箱：&#x27;</span>, <span class="variable language_">window</span>.<span class="property">a</span>);</span><br><span class="line">diffSandbox.<span class="title function_">inactive</span>();</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;失活沙箱：&#x27;</span>, <span class="variable language_">window</span>.<span class="property">a</span>);</span><br><span class="line">diffSandbox.<span class="title function_">active</span>();</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;再次激活&#x27;</span>, <span class="variable language_">window</span>.<span class="property">a</span>);</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">MultipleProxySandbox</span> &#123;</span><br><span class="line">    <span class="title function_">active</span>(<span class="params"></span>) &#123;</span><br><span class="line">        <span class="variable language_">this</span>.<span class="property">sandboxRunning</span> = <span class="literal">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="title function_">inactive</span>(<span class="params"></span>) &#123;</span><br><span class="line">        <span class="variable language_">this</span>.<span class="property">sandboxRunning</span> = <span class="literal">false</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="title function_">constructor</span>(<span class="params">name, context = &#123;&#125;</span>) &#123;  <span class="comment">// 沙箱名称、共享的上下文</span></span><br><span class="line">        <span class="variable language_">this</span>.<span class="property">name</span> = name;</span><br><span class="line">        <span class="variable language_">this</span>.<span class="property">proxy</span> = <span class="literal">null</span>;</span><br><span class="line">        <span class="keyword">const</span> fakeWindow = <span class="title class_">Object</span>.<span class="title function_">create</span>(&#123;&#125;);</span><br><span class="line">        <span class="keyword">const</span> proxy = <span class="keyword">new</span> <span class="title class_">Proxy</span>(fakeWindow, &#123;</span><br><span class="line">            <span class="attr">set</span>: <span class="function">(<span class="params">target, name, value</span>) =&gt;</span> &#123;</span><br><span class="line">                <span class="keyword">if</span> (<span class="variable language_">this</span>.<span class="property">sandboxRunning</span>) &#123;</span><br><span class="line">                    <span class="keyword">if</span> (<span class="title class_">Object</span>.<span class="title function_">keys</span>(context).<span class="title function_">includes</span>(name)) &#123;</span><br><span class="line">                        context[name] = value;</span><br><span class="line">                    &#125;</span><br><span class="line">                    target[name] = value;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;,</span><br><span class="line">            <span class="attr">get</span>: <span class="function">(<span class="params">target, name</span>) =&gt;</span> &#123;</span><br><span class="line">                <span class="keyword">if</span> (<span class="title class_">Object</span>.<span class="title function_">keys</span>(context).<span class="title function_">includes</span>(name)) &#123;</span><br><span class="line">                    <span class="keyword">return</span> context[name];  <span class="comment">// 优先使用共享对象</span></span><br><span class="line">                &#125;</span><br><span class="line">                <span class="keyword">return</span> target[name];</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;)</span><br><span class="line">        <span class="variable language_">this</span>.<span class="property">proxy</span> = proxy;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> context = &#123; <span class="attr">document</span>: <span class="variable language_">window</span>.<span class="property">document</span> &#125;;</span><br><span class="line"><span class="keyword">const</span> newSandBox1 = <span class="keyword">new</span> <span class="title class_">MultipleProxySandbox</span>(<span class="string">&#x27;代理沙箱1&#x27;</span>, context);</span><br><span class="line">newSandBox1.<span class="title function_">active</span>();</span><br><span class="line"><span class="keyword">const</span> proxyWindow1 = newSandBox1.<span class="property">proxy</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> newSandBox2 = <span class="keyword">new</span> <span class="title class_">MultipleProxySandbox</span>(<span class="string">&#x27;代理沙箱2&#x27;</span>, context);</span><br><span class="line">newSandBox2.<span class="title function_">active</span>();</span><br><span class="line"></span><br><span class="line">proxyWindow1.<span class="property">a</span> = <span class="string">&#x27;1&#x27;</span>;</span><br><span class="line">proxyWindow2.<span class="property">a</span> = <span class="string">&#x27;2&#x27;</span>;</span><br><span class="line"><span class="variable language_">window</span>.<span class="property">a</span> = <span class="string">&#x27;3&#x27;</span>;</span><br></pre></td></tr></table></figure>


<h2 id="技术实战"><a href="#技术实战" class="headerlink" title="技术实战"></a>技术实战</h2>  <figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br><span class="line">177</span><br><span class="line">178</span><br><span class="line">179</span><br><span class="line">180</span><br><span class="line">181</span><br><span class="line">182</span><br><span class="line">183</span><br><span class="line">184</span><br><span class="line">185</span><br><span class="line">186</span><br><span class="line">187</span><br><span class="line">188</span><br><span class="line">189</span><br><span class="line">190</span><br><span class="line">191</span><br><span class="line">192</span><br><span class="line">193</span><br><span class="line">194</span><br><span class="line">195</span><br><span class="line">196</span><br><span class="line">197</span><br><span class="line">198</span><br><span class="line">199</span><br><span class="line">200</span><br><span class="line">201</span><br><span class="line">202</span><br><span class="line">203</span><br><span class="line">204</span><br><span class="line">205</span><br><span class="line">206</span><br><span class="line">207</span><br><span class="line">208</span><br><span class="line">209</span><br><span class="line">210</span><br><span class="line">211</span><br><span class="line">212</span><br><span class="line">213</span><br><span class="line">214</span><br><span class="line">215</span><br><span class="line">216</span><br><span class="line">217</span><br><span class="line">218</span><br><span class="line">219</span><br><span class="line">220</span><br><span class="line">221</span><br><span class="line">222</span><br><span class="line">223</span><br><span class="line">224</span><br><span class="line">225</span><br><span class="line">226</span><br><span class="line">227</span><br><span class="line">228</span><br><span class="line">229</span><br><span class="line">230</span><br><span class="line">231</span><br><span class="line">232</span><br><span class="line">233</span><br><span class="line">234</span><br><span class="line">235</span><br><span class="line">236</span><br><span class="line">237</span><br><span class="line">238</span><br><span class="line">239</span><br><span class="line">240</span><br><span class="line">241</span><br><span class="line">242</span><br><span class="line">243</span><br><span class="line">244</span><br><span class="line">245</span><br><span class="line">246</span><br><span class="line">247</span><br><span class="line">248</span><br><span class="line">249</span><br><span class="line">250</span><br><span class="line">251</span><br><span class="line">252</span><br><span class="line">253</span><br><span class="line">254</span><br><span class="line">255</span><br><span class="line">256</span><br><span class="line">257</span><br><span class="line">258</span><br><span class="line">259</span><br><span class="line">260</span><br><span class="line">261</span><br><span class="line">262</span><br><span class="line">263</span><br><span class="line">264</span><br><span class="line">265</span><br><span class="line">266</span><br><span class="line">267</span><br><span class="line">268</span><br><span class="line">269</span><br><span class="line">270</span><br><span class="line">271</span><br><span class="line">272</span><br><span class="line">273</span><br><span class="line">274</span><br><span class="line">275</span><br><span class="line">276</span><br><span class="line">277</span><br><span class="line">278</span><br><span class="line">279</span><br><span class="line">280</span><br><span class="line">281</span><br><span class="line">282</span><br><span class="line">283</span><br><span class="line">284</span><br><span class="line">285</span><br><span class="line">286</span><br><span class="line">287</span><br><span class="line">288</span><br><span class="line">289</span><br><span class="line">290</span><br><span class="line">291</span><br><span class="line">292</span><br><span class="line">293</span><br><span class="line">294</span><br><span class="line">295</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 1、初始化项目并新建文件</span></span><br><span class="line">npm install -g pnpm</span><br><span class="line">pnpm init</span><br><span class="line"></span><br><span class="line">├── packages       <span class="comment">// 放置所有的微前端应用(基座和子应用)、共享模块</span></span><br><span class="line">|   ├── apps</span><br><span class="line">|   └── modules</span><br><span class="line">├── package.<span class="property">json</span></span><br><span class="line">└── pnpm-workspace.<span class="property">yaml</span>          <span class="comment">// workspace 根目录的配置文件</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">// 2、MicroApp 框架安装：可以有多个基座应用，每个都可以包含多个子应用</span></span><br><span class="line">pnpm add micro-app -w      <span class="comment">// -w 安装到根目录，可保证基座应用统一</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">// 3、Vite 搭建基座应用：会提示输入项目名，新建基座目录 packages/base</span></span><br><span class="line">pnpm add vite -D</span><br><span class="line">pnpm create vite --template vue packages/base</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">// 4、vue-cli 搭建子应用：如果使用 vite 搭建则需要一些配置才能使用</span></span><br><span class="line"><span class="comment">// router/index.ts</span></span><br><span class="line"><span class="keyword">import</span> &#123; createRouter, createWebHistory &#125; <span class="keyword">from</span> <span class="string">&#x27;vue-router&#x27;</span></span><br><span class="line"><span class="keyword">import</span> routes <span class="keyword">from</span> <span class="string">&#x27;./router&#x27;</span></span><br><span class="line"><span class="keyword">const</span> router = <span class="title function_">createRouter</span>(&#123;</span><br><span class="line">    <span class="attr">history</span>: <span class="title function_">createWebHistory</span>(<span class="variable language_">window</span>.<span class="property">__MICRO_APP_BASE_ROUTE__</span> </span><br><span class="line">    || process.<span class="property">env</span>.<span class="property">BASE_URL</span>),</span><br><span class="line">    routes,</span><br><span class="line">&#125;)</span><br><span class="line"><span class="comment">// public-path.js：src 目录下创建并在 main.js 中引入 Micro 基础路由</span></span><br><span class="line"><span class="keyword">if</span> (<span class="variable language_">window</span>.<span class="property">__MICRO_APP_ENVIRONMENT__</span>) &#123;</span><br><span class="line">    __webpack_public_path__ = <span class="variable language_">window</span>.<span class="property">__MICRO_APP_PUBLIC_PATH__</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// webpack-dev-server：开发环境配置子应用跨域，线上环境则配置 nginx</span></span><br><span class="line"><span class="attr">devServer</span>: &#123;</span><br><span class="line">    <span class="attr">headers</span>: &#123; <span class="string">&#x27;Access-Control-Allow-Origin&#x27;</span>: <span class="string">&#x27;*&#x27;</span> &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">// 5、根目录新建统一的应用配置文件 config.json</span></span><br><span class="line"><span class="string">&quot;base&quot;</span>: &#123;</span><br><span class="line">    <span class="string">&quot;port&quot;</span>: <span class="number">4000</span>,</span><br><span class="line">    <span class="string">&quot;name&quot;</span>: <span class="string">&quot;基座&quot;</span>,</span><br><span class="line">    <span class="string">&quot;packageName&quot;</span>: <span class="string">&quot;@package/core&quot;</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">// 6、路由配置</span></span><br><span class="line"><span class="comment">// 基座应用创建 src/config.[ts|js]：用来修改不同环境的地址</span></span><br><span class="line"><span class="keyword">import</span> packageConfig <span class="keyword">from</span> <span class="string">&#x27;@/../../../config.json&#x27;</span>;</span><br><span class="line"><span class="keyword">const</span> config = <span class="title class_">JSON</span>.<span class="title function_">parse</span>(<span class="title class_">JSON</span>.<span class="title function_">stringify</span>(packageConfig));</span><br><span class="line">interface <span class="title class_">DevConfig</span> &#123;</span><br><span class="line">    [<span class="attr">key</span>: string]: string;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">const</span> <span class="attr">devConfig</span>: <span class="title class_">DevConfig</span> = &#123;&#125;;</span><br><span class="line"><span class="title class_">Object</span>.<span class="title function_">keys</span>(config).<span class="title function_">forEach</span>(<span class="function">(<span class="params">key</span>) =&gt;</span> &#123;  <span class="comment">// 合并基础配置</span></span><br><span class="line">    <span class="keyword">if</span> (key !== <span class="string">&#x27;base&#x27;</span>) &#123;</span><br><span class="line">        devConfig[key] = <span class="string">`http://localhost:<span class="subst">$&#123;config[key].port&#125;</span>`</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;);</span><br><span class="line"><span class="keyword">if</span> (process.<span class="property">env</span>.<span class="property">NODE_ENV</span> === <span class="string">&#x27;production&#x27;</span>) &#123;  <span class="comment">// 线上环境补全地址</span></span><br><span class="line">    <span class="title class_">Object</span>.<span class="title function_">keys</span>(devConfig).<span class="title function_">forEach</span>(<span class="function">(<span class="params">key</span>) =&gt;</span> &#123;</span><br><span class="line">        devConfig[key] = <span class="variable language_">window</span>.<span class="property">location</span>.<span class="property">origin</span>;</span><br><span class="line">    &#125;);</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> devConfig;</span><br><span class="line"><span class="comment">// 基座应用中创建 src/router/index.ts：用来配置路由</span></span><br><span class="line"><span class="keyword">import</span> &#123;createRouter,createWebHistory,<span class="title class_">RouteRecordRaw</span>&#125; <span class="keyword">from</span> <span class="string">&#x27;vue-router&#x27;</span>;</span><br><span class="line"><span class="keyword">const</span> <span class="attr">routes</span>: <span class="title class_">Array</span>&lt;<span class="title class_">RouteRecordRaw</span>&gt; = [];</span><br><span class="line"><span class="keyword">const</span> router = <span class="title function_">createRouter</span>(&#123;</span><br><span class="line">    <span class="attr">history</span>: <span class="title function_">createWebHistory</span>(<span class="keyword">import</span>.<span class="property">meta</span>.<span class="property">env</span>.<span class="property">BASE_URL</span>),</span><br><span class="line">    routes</span><br><span class="line">&#125;);</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> router;</span><br><span class="line"><span class="comment">// 基座根据配置文件自动生成子应用页面组件</span></span><br><span class="line"><span class="keyword">import</span> &#123; defineComponent &#125; <span class="keyword">from</span> <span class="string">&#x27;vue&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> packageConfig <span class="keyword">from</span> <span class="string">&#x27;@/../../../config.json&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> devConfig <span class="keyword">from</span> <span class="string">&#x27;@/config&#x27;</span>;</span><br><span class="line"><span class="keyword">const</span> config = <span class="title class_">JSON</span>.<span class="title function_">parse</span>(<span class="title class_">JSON</span>.<span class="title function_">stringify</span>(packageConfig));</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="keyword">function</span> <span class="title function_">buildPage</span>(<span class="params">name: string</span>) &#123;</span><br><span class="line">    <span class="keyword">const</span> url = <span class="string">`<span class="subst">$&#123;devConfig[name]&#125;</span>/child/<span class="subst">$&#123;name&#125;</span>`</span>;</span><br><span class="line">    <span class="keyword">return</span> <span class="title function_">defineComponent</span>(&#123;</span><br><span class="line">        name,</span><br><span class="line">        <span class="title function_">setup</span>(<span class="params"></span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="function">() =&gt;</span> <span class="language-xml"><span class="tag">&lt;<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">                <span class="tag">&lt;<span class="name">micro-app</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">                    <span class="attr">name</span>=<span class="string">&#123;name&#125;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">                    <span class="attr">url</span>=<span class="string">&#123;url&#125;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">                    <span class="attr">baseroute</span>=<span class="string">&#123;</span>`/<span class="attr">base</span>/$&#123;<span class="attr">name</span>&#125;`&#125;</span></span></span><br><span class="line"><span class="tag"><span class="language-xml">                    <span class="attr">disableScopecss</span>=<span class="string">&#123;config[name].disableScopecss&#125;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">                &gt;</span><span class="tag">&lt;/<span class="name">micro-app</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">            <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;);</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 基座根据配置文件自动生成子应用路由</span></span><br><span class="line"><span class="keyword">import</span> packageConfig <span class="keyword">from</span> <span class="string">&#x27;@/../../../config.json&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> buildPage <span class="keyword">from</span> <span class="string">&#x27;@/views/buildPage&#x27;</span>;</span><br><span class="line"><span class="keyword">const</span> config = <span class="title class_">JSON</span>.<span class="title function_">parse</span>(<span class="title class_">JSON</span>.<span class="title function_">stringify</span>(packageConfig));</span><br><span class="line"><span class="keyword">function</span> <span class="title function_">buildMicroRoutes</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">const</span> <span class="attr">routes</span>: <span class="title class_">Array</span>&lt;<span class="title class_">RouteRecordRaw</span>&gt; = [];</span><br><span class="line">    <span class="title class_">Object</span>.<span class="title function_">keys</span>(config).<span class="title function_">forEach</span>(<span class="function">(<span class="params">key: string</span>) =&gt;</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (key !== <span class="string">&#x27;base&#x27;</span>) &#123;</span><br><span class="line">            routes.<span class="title function_">push</span>(&#123;</span><br><span class="line">                <span class="attr">path</span>: <span class="string">`/<span class="subst">$&#123;key&#125;</span>/:page*`</span>,</span><br><span class="line">                <span class="attr">name</span>: key.<span class="title function_">charAt</span>(<span class="number">0</span>).<span class="title function_">toUpperCase</span>() + key.<span class="title function_">slice</span>(<span class="number">1</span>),</span><br><span class="line">                <span class="attr">component</span>: <span class="title function_">buildPage</span>(key),</span><br><span class="line">                <span class="attr">meta</span>: &#123; <span class="attr">auth</span>: config[key].<span class="property">auth</span> &#125;,</span><br><span class="line">            &#125;);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;);</span><br><span class="line">    <span class="keyword">return</span> routes;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">const</span> <span class="attr">routes</span>: <span class="title class_">Array</span>&lt;<span class="title class_">RouteRecordRaw</span>&gt; = [</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="attr">path</span>: <span class="string">&#x27;/&#x27;</span>,</span><br><span class="line">        <span class="attr">name</span>: <span class="string">&#x27;base&#x27;</span>,</span><br><span class="line">        <span class="attr">redirect</span>: <span class="string">&#x27;/core&#x27;</span>,</span><br><span class="line">        <span class="attr">children</span>: <span class="title function_">buildMicroRoutes</span>(),</span><br><span class="line">    &#125;</span><br><span class="line">];</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">// 7、基座登录权限管理：base/src 目录新建 permission.ts</span></span><br><span class="line"><span class="keyword">import</span> storage <span class="keyword">from</span> <span class="string">&#x27;store&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> router <span class="keyword">from</span> <span class="string">&#x27;./router/index&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> useUserStore <span class="keyword">from</span> <span class="string">&#x27;./stores/user&#x27;</span>;</span><br><span class="line"><span class="keyword">const</span> loginRoutePath = <span class="string">&#x27;/login&#x27;</span>;</span><br><span class="line"><span class="keyword">const</span> defaultRoutePath = <span class="string">&#x27;/home&#x27;</span>;</span><br><span class="line">router.<span class="title function_">beforeEach</span>(<span class="keyword">async</span> (to, <span class="keyword">from</span>, next) =&gt; &#123;</span><br><span class="line">    <span class="keyword">const</span> token = storage.<span class="title function_">get</span>(<span class="string">&#x27;authKey&#x27;</span>);</span><br><span class="line">    <span class="title class_">NProgress</span>.<span class="title function_">start</span>();</span><br><span class="line">    <span class="keyword">if</span> (to.<span class="property">matched</span>.<span class="title function_">some</span>(<span class="function"><span class="params">r</span> =&gt;</span> r.<span class="property">meta</span>.<span class="property">auth</span>)) &#123;  <span class="comment">// config.json 添加 auth</span></span><br><span class="line">        <span class="keyword">if</span> (token &amp;&amp; token !== <span class="string">&#x27;undefined&#x27;</span>) &#123;</span><br><span class="line">            <span class="keyword">if</span> (to.<span class="property">path</span> === loginRoutePath) &#123;</span><br><span class="line">                <span class="title function_">next</span>(&#123; <span class="attr">path</span>: defaultRoutePath &#125;);</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                <span class="title function_">next</span>();</span><br><span class="line">            &#125;</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;   <span class="comment">// 没有登录时跳转到登录界面，携带上登录成功后要跳转的页面路径</span></span><br><span class="line">            <span class="title function_">next</span>(&#123; <span class="attr">name</span>: <span class="string">&#x27;Login&#x27;</span>, <span class="attr">query</span>: &#123; <span class="attr">redirect</span>: to.<span class="property">fullPath</span> &#125; &#125;);</span><br><span class="line">            <span class="title class_">NProgress</span>.<span class="title function_">done</span>();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="title function_">next</span>();   <span class="comment">// 不需要身份校验 直接通过</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">// 8、应用之间跳转</span></span><br><span class="line"><span class="keyword">import</span> microApp, &#123; getActiveApps &#125; <span class="keyword">from</span> <span class="string">&#x27;@micro-zoe/micro-app&#x27;</span>;</span><br><span class="line"><span class="keyword">if</span> (!<span class="title function_">getActiveApps</span>().<span class="title function_">includes</span>(appName)) &#123;</span><br><span class="line">    router.<span class="title function_">push</span>(<span class="string">`/<span class="subst">$&#123;appName&#125;</span><span class="subst">$&#123;path&#125;</span>`</span>);</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;     <span class="comment">// 跳转页面是已开启的子应用，则通知子应用自行跳转</span></span><br><span class="line">    microApp.<span class="title function_">setData</span>(appName, &#123; path &#125;);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">router.<span class="title function_">afterEach</span>(<span class="function">(<span class="params">&#123; path &#125;</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="comment">// 通过子应用向基座发送当前的路由信息</span></span><br><span class="line">    <span class="variable language_">window</span>.<span class="property">microApp</span>.<span class="title function_">dispatch</span>(&#123; path &#125;);</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line">microApp.<span class="title function_">addDataListener</span>(appName, <span class="function">(<span class="params">&#123; path &#125;</span>) =&gt;</span> &#123;  </span><br><span class="line">    <span class="comment">// 导航栏重新激活当前菜单</span></span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">// 9、基座与子应用通讯</span></span><br><span class="line"><span class="comment">// 子应用</span></span><br><span class="line"><span class="variable language_">window</span>.<span class="property">microApp</span>.<span class="title function_">getData</span>()          <span class="comment">// 子应用获取基座下发的数据。</span></span><br><span class="line"><span class="variable language_">window</span>.<span class="property">microApp</span>.<span class="title function_">addDataListener</span>()  <span class="comment">// 子应用监听基座下发的数据。</span></span><br><span class="line"><span class="variable language_">window</span>.<span class="property">microApp</span>.<span class="title function_">dispatch</span>(&#123; <span class="attr">type</span>: <span class="string">&#x27;子应用向基座发送的数据&#x27;</span> &#125;)</span><br><span class="line"><span class="comment">// 基座</span></span><br><span class="line">microApp.<span class="title function_">setData</span>(<span class="string">&#x27;子应用name&#x27;</span>, &#123; <span class="attr">type</span>: <span class="string">&#x27;基座手动发送数据&#x27;</span> &#125;)</span><br><span class="line">可通过组件 data 属性传递数据，请参考 <span class="title class_">MicroApp</span> vue/react 组件。</span><br><span class="line">microApp.<span class="title function_">getData</span>(appName)     <span class="comment">// 获取子应用发送的数据。</span></span><br><span class="line">microApp.<span class="title function_">addDataListener</span>()    <span class="comment">// 监听子应用发送的数据。</span></span><br><span class="line">可通过组件 @datachange 属性监听子应用发送的数据。</span><br><span class="line"><span class="comment">// 全局数据</span></span><br><span class="line">microApp.<span class="title function_">getGlobalData</span>()          <span class="comment">// 返回全局数据。</span></span><br><span class="line">microApp.<span class="title function_">addGlobalDataListener</span>()  <span class="comment">// 监听全局数据变化。</span></span><br><span class="line"><span class="comment">// 本地存储：推荐安装 store 库并对存储数据进行加密处理和容量限制</span></span><br><span class="line"><span class="comment">// 基座应用创建 bus.js 统一管理和子应用的通讯：遍历挂载的 addDataListener</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">// 10、搭建共享通用功能模块：vue 组件推荐 vue-demi，下面以封装 axios 为例。</span></span><br><span class="line"><span class="keyword">export</span> interface <span class="title class_">Response</span>&lt;T&gt; &#123;</span><br><span class="line">    <span class="attr">error</span>: <span class="title class_">Error</span>;</span><br><span class="line">    <span class="attr">response</span>: <span class="title class_">ResponseResult</span>&lt;T&gt;;</span><br><span class="line">    <span class="attr">response_code</span>: number;</span><br><span class="line">    <span class="attr">success</span>: boolean;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">export</span> interface <span class="title class_">ListResult</span>&lt;T&gt; &#123;</span><br><span class="line">    countId?: string;</span><br><span class="line">    <span class="attr">current</span>: number;</span><br><span class="line">    <span class="attr">hitCount</span>: boolean;</span><br><span class="line">    maxLimit?: string;</span><br><span class="line">    <span class="attr">optimizeCountSql</span>: boolean;</span><br><span class="line">    <span class="attr">orders</span>: [];</span><br><span class="line">    <span class="attr">pages</span>: number;</span><br><span class="line">    <span class="attr">records</span>: T[];</span><br><span class="line">    <span class="attr">searchCount</span>: boolean;</span><br><span class="line">    <span class="attr">size</span>: number;</span><br><span class="line">    <span class="attr">total</span>: number;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 定义接口时直接引用上述类型</span></span><br><span class="line"><span class="keyword">import</span> &#123; request, <span class="title class_">Response</span>, <span class="title class_">ListResult</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;@package/share&#x27;</span>;</span><br><span class="line">interface <span class="title class_">Result</span> &#123; &#125;</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">function</span> <span class="title function_">getChargingAnalysis</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> request&lt;<span class="title class_">Response</span>&lt;<span class="title class_">ListResult</span>&lt;<span class="title class_">Result</span>&gt;&gt;&gt;(&#123;</span><br><span class="line">        <span class="attr">url</span>: <span class="string">&#x27;&#x27;</span>,</span><br><span class="line">        <span class="attr">method</span>: <span class="string">&#x27;post&#x27;</span>,</span><br><span class="line">    &#125;);</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// package.json 增加依赖</span></span><br><span class="line"><span class="string">&quot;dependencies&quot;</span>: &#123;  <span class="string">&quot;@package/share&quot;</span>: <span class="string">&quot;workspace:*&quot;</span> &#125;,</span><br><span class="line"><span class="comment">// 使用 rollup 打包时配置如下</span></span><br><span class="line"><span class="keyword">import</span> typescript <span class="keyword">from</span> <span class="string">&#x27;rollup-plugin-typescript2&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> autoprefixer <span class="keyword">from</span> <span class="string">&#x27;autoprefixer&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> pkg <span class="keyword">from</span> <span class="string">&#x27;./package.json&#x27;</span>;</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> &#123;</span><br><span class="line">  <span class="attr">input</span>: <span class="string">&#x27;src/index.ts&#x27;</span>, <span class="comment">// 入口文件</span></span><br><span class="line">  <span class="attr">output</span>: [&#123; <span class="attr">file</span>: pkg.<span class="property">main</span>, <span class="attr">format</span>: <span class="string">&#x27;esm&#x27;</span>, <span class="attr">sourcemap</span>: <span class="literal">false</span> &#125;],</span><br><span class="line">  <span class="attr">plugins</span>: [<span class="title function_">typescript</span>(&#123;</span><br><span class="line">      <span class="attr">tsconfigOverride</span>: &#123;</span><br><span class="line">        <span class="attr">compilerOptions</span>: &#123; <span class="attr">declaration</span>: <span class="literal">true</span>, <span class="attr">module</span>: <span class="string">&#x27;ESNext&#x27;</span> &#125;,</span><br><span class="line">      &#125;,</span><br><span class="line">      <span class="attr">check</span>: <span class="literal">false</span>,</span><br><span class="line">  &#125;)]</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">// 11、统一代码规范：根目录 .eslintrc.js、.eslintignore、.editorconfig</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">// 12、Mock 模拟数据和接口：根目录创建目录 mocks</span></span><br><span class="line">pnpm add -w msw faker -D</span><br><span class="line"><span class="comment">// mocks/task/history.js</span></span><br><span class="line"><span class="keyword">import</span> &#123; rest &#125; <span class="keyword">from</span> <span class="string">&#x27;msw&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; faker &#125; <span class="keyword">from</span> <span class="string">&#x27;@faker-js/faker/locale/zh_CN&#x27;</span>;</span><br><span class="line"><span class="keyword">const</span> baseUrl = process.<span class="property">env</span>.<span class="property">VUE_APP_API_BASE_URL</span>;</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> [</span><br><span class="line">  rest.<span class="title function_">post</span>(<span class="string">`<span class="subst">$&#123;baseUrl&#125;</span>/business/history/page`</span>, <span class="function">(<span class="params">req, res, ctx</span>) =&gt;</span> <span class="title function_">res</span>(</span><br><span class="line">    ctx.<span class="title function_">delay</span>(),</span><br><span class="line">    ctx.<span class="title function_">status</span>(<span class="number">200</span>),</span><br><span class="line">    ctx.<span class="title function_">json</span>(&#123;</span><br><span class="line">      <span class="attr">error</span>: <span class="literal">null</span>,</span><br><span class="line">      <span class="attr">success</span>: <span class="literal">true</span>,</span><br><span class="line">      <span class="attr">response_code</span>: <span class="number">2000</span>,</span><br><span class="line">      <span class="attr">response</span>: &#123;</span><br><span class="line">          <span class="attr">code</span>: <span class="number">200</span>,</span><br><span class="line">          <span class="attr">message</span>: <span class="string">&#x27;检索成功。&#x27;</span>,</span><br><span class="line">          <span class="attr">result</span>: &#123;</span><br><span class="line">              <span class="attr">current</span>: req.<span class="property">body</span>.<span class="property">pageNo</span>,</span><br><span class="line">              <span class="attr">size</span>: req.<span class="property">body</span>.<span class="property">pageSize</span>,</span><br><span class="line">              <span class="attr">total</span>: faker.<span class="property">datatype</span>.<span class="title function_">number</span>(&#123; <span class="attr">min</span>: <span class="number">100</span>, <span class="attr">max</span>: <span class="number">500</span> &#125;),</span><br><span class="line">              <span class="attr">records</span>: <span class="keyword">new</span> <span class="title class_">Array</span>(req.<span class="property">body</span>.<span class="property">pageSize</span>).<span class="title function_">fill</span>(<span class="number">1</span>).<span class="title function_">map</span>(<span class="function">() =&gt;</span> (&#123;</span><br><span class="line">                  <span class="attr">agencyCode</span>: faker.<span class="property">random</span>.<span class="title function_">word</span>(),</span><br><span class="line">                  <span class="attr">createTime</span>: faker.<span class="property">date</span>.<span class="title function_">past</span>(),</span><br><span class="line">                  <span class="attr">createUser</span>: faker.<span class="property">datatype</span>.<span class="title function_">number</span>(),</span><br><span class="line">                  <span class="attr">dataHash</span>: faker.<span class="property">random</span>.<span class="title function_">word</span>(),</span><br><span class="line">                  <span class="attr">id</span>: faker.<span class="property">datatype</span>.<span class="title function_">uuid</span>(),</span><br><span class="line">                  <span class="attr">updateTime</span>: faker.<span class="property">date</span>.<span class="title function_">past</span>(),</span><br><span class="line">                  <span class="attr">updateUser</span>: faker.<span class="property">datatype</span>.<span class="title function_">number</span>(),</span><br><span class="line">                  <span class="attr">vin</span>: faker.<span class="property">vehicle</span>.<span class="title function_">vin</span>()</span><br><span class="line">              &#125;))</span><br><span class="line">          &#125;</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;)</span><br><span class="line">  ))</span><br><span class="line">];</span><br><span class="line"><span class="comment">// mocks/handlers.js</span></span><br><span class="line"><span class="keyword">import</span> taskHistory <span class="keyword">from</span> <span class="string">&#x27;./task/history&#x27;</span>;</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> handlers = [...taskHistory];</span><br><span class="line"><span class="comment">// mocks/browser.js：用于启动浏览器环境 mock</span></span><br><span class="line"><span class="keyword">import</span> &#123; setupWorker &#125; <span class="keyword">from</span> <span class="string">&#x27;msw&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; handlers &#125; <span class="keyword">from</span> <span class="string">&#x27;./handlers&#x27;</span>;</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> worker = <span class="title function_">setupWorker</span>(...handlers);</span><br><span class="line"><span class="comment">// 执行初始化命令</span></span><br><span class="line">npx msw init ./packages/base/public --save</span><br><span class="line"><span class="comment">// 应用 main.js 添加以下代码即可使用</span></span><br><span class="line"><span class="keyword">import</span> config <span class="keyword">from</span> <span class="string">&#x27;../../../config.json&#x27;</span>;</span><br><span class="line"><span class="keyword">const</span> microAppName = packageJson.<span class="property">name</span>.<span class="title function_">split</span>(<span class="string">&#x27;/&#x27;</span>)[<span class="number">1</span>];</span><br><span class="line"><span class="keyword">const</span> isMock = config[microAppName].<span class="property">mock</span>;  <span class="comment">// 开关字段</span></span><br><span class="line"><span class="keyword">if</span> (process.<span class="property">env</span>.<span class="property">NODE_ENV</span> === <span class="string">&#x27;development&#x27;</span> &amp;&amp; isMock) &#123;</span><br><span class="line">    <span class="keyword">const</span> &#123; worker &#125; = <span class="built_in">require</span>(<span class="string">&#x27;../../../mocks/browser&#x27;</span>);</span><br><span class="line">    worker.<span class="title function_">start</span>();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">// 13、接入旧项目的一般步骤</span></span><br><span class="line">将旧项目拷贝到 packages 目录，config.<span class="property">json</span> 中增加此应用的配置。</span><br><span class="line">查看是否有公共依赖相同依赖，可考虑升级或降级并使用 pnpm 重新安装。</span><br><span class="line">旧项目的导航栏、登录等功能组件与基座功能冲突，删除后仅保留页面内容。</span><br><span class="line">补充上下文提到过子应用的配置，补全删除登录功能后的数据交互。</span><br></pre></td></tr></table></figure>


<h2 id="开发构建"><a href="#开发构建" class="headerlink" title="开发构建"></a>开发构建</h2>  <figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@title</span> 1、优化打包命令 npm script：多个应用打包时需编辑多条 npm script</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> <span class="variable">npm</span>-run-all：安装后可同时运行多个，但每次新增应用时都需要添加配置</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> node 脚本启动和创建应用：根目录下创建 scripts/dev.js、build.js</span></span><br><span class="line"><span class="comment">**/</span> </span><br><span class="line"><span class="comment">// package.json 配置 npm script</span></span><br><span class="line"><span class="string">&quot;build&quot;</span>: <span class="string">&quot;npm-run-all build:*&quot;</span>,</span><br><span class="line"><span class="string">&quot;build:base&quot;</span>: <span class="string">&quot;cd packages/base &amp;&amp; npm run build&quot;</span>,</span><br><span class="line"><span class="string">&quot;build:core&quot;</span>: <span class="string">&quot;cd packages/core &amp;&amp; npm run build&quot;</span>,</span><br><span class="line"></span><br><span class="line"><span class="string">&quot;dev&quot;</span>: <span class="string">&quot;node scripts/dev.js&quot;</span>,</span><br><span class="line"><span class="string">&quot;build&quot;</span>: <span class="string">&quot;node scripts/build.js&quot;</span>,</span><br><span class="line"></span><br><span class="line"><span class="comment">// scripts/dev.js：先判断基座是否启动，没有泽启动，然后出现列表来选择子应用</span></span><br><span class="line"><span class="keyword">const</span> inquirer = <span class="built_in">require</span>(<span class="string">&#x27;inquirer&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> execa = <span class="built_in">require</span>(<span class="string">&#x27;execa&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> detect = <span class="built_in">require</span>(<span class="string">&#x27;detect-port&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> config = <span class="built_in">require</span>(<span class="string">&#x27;../config.json&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> occupiedList = [];  <span class="comment">// 已占用端口列表</span></span><br><span class="line"><span class="keyword">const</span> checkPorts = <span class="title class_">Object</span>.<span class="title function_">keys</span>(config).<span class="title function_">map</span>(<span class="function">(<span class="params">key</span>) =&gt;</span> </span><br><span class="line"><span class="keyword">new</span> <span class="title class_">Promise</span>(<span class="function"><span class="params">resolve</span> =&gt;</span> &#123;</span><br><span class="line">    <span class="title function_">detect</span>(config[key].<span class="property">port</span>).<span class="title function_">then</span>(<span class="function">(<span class="params">port</span>) =&gt;</span> &#123;    <span class="comment">// 用于检测端口是否被占用</span></span><br><span class="line">        <span class="title function_">resolve</span>(&#123; <span class="attr">package</span>: key, <span class="attr">isOccupied</span>: port !== config[key].<span class="property">port</span> &#125;);</span><br><span class="line">    &#125;);</span><br><span class="line">&#125;));</span><br><span class="line"><span class="keyword">function</span> <span class="title function_">runInquirerCommand</span>(<span class="params"></span>) &#123;  <span class="comment">// inquirer 命令行根据用户输入信息后执行操作。</span></span><br><span class="line">    inquirer.<span class="title function_">prompt</span>([&#123;</span><br><span class="line">        <span class="attr">name</span>: <span class="string">&#x27;devPackage&#x27;</span>,</span><br><span class="line">        <span class="attr">type</span>: <span class="string">&#x27;list&#x27;</span>,</span><br><span class="line">        <span class="attr">message</span>: <span class="string">&#x27;请选择要启动的子应用&#x27;</span>,</span><br><span class="line">        <span class="attr">choices</span>: <span class="title class_">Object</span>.<span class="title function_">keys</span>(config).<span class="title function_">filter</span>(<span class="function"><span class="params">item</span> =&gt;</span> item !== <span class="string">&#x27;base&#x27;</span>)</span><br><span class="line">        .<span class="title function_">map</span>(<span class="function"><span class="params">key</span> =&gt;</span> &#123;</span><br><span class="line">            <span class="keyword">const</span> &#123; name, packageName, port &#125; = config[key];</span><br><span class="line">            <span class="keyword">return</span> &#123;</span><br><span class="line">                <span class="attr">name</span>: <span class="string">`<span class="subst">$&#123;name&#125;</span>(<span class="subst">$&#123;packageName&#125;</span>:<span class="subst">$&#123;port&#125;</span>)`</span>,</span><br><span class="line">                <span class="attr">value</span>: key,</span><br><span class="line">                <span class="attr">disabled</span>: occupiedList.<span class="title function_">find</span>(<span class="function"><span class="params">item</span> =&gt;</span> item.<span class="property">package</span> == key)</span><br><span class="line">                .<span class="property">isOccupied</span> ? <span class="string">&#x27;已启动&#x27;</span> : <span class="literal">false</span></span><br><span class="line">            &#125;;</span><br><span class="line">        &#125;)</span><br><span class="line">    &#125;])</span><br><span class="line">    .<span class="title function_">then</span>(<span class="keyword">async</span> (answers) =&gt; &#123;</span><br><span class="line">        <span class="title function_">execa</span>(<span class="string">&#x27;npm&#x27;</span>, [<span class="string">&#x27;run&#x27;</span>, <span class="string">&#x27;dev&#x27;</span>], &#123;  <span class="comment">// 用于在子进程中运行外部命令</span></span><br><span class="line">            <span class="attr">cwd</span>: <span class="string">`./packages/<span class="subst">$&#123;answers.devPackage&#125;</span>`</span>,</span><br><span class="line">            <span class="attr">stdio</span>: <span class="string">&#x27;inherit&#x27;</span>,</span><br><span class="line">        &#125;);</span><br><span class="line">    &#125;);</span><br><span class="line">&#125;</span><br><span class="line"><span class="title class_">Promise</span>.<span class="title function_">all</span>(checkPorts).<span class="title function_">then</span>(<span class="function">(<span class="params">ports</span>) =&gt;</span> &#123;</span><br><span class="line">    occupiedList.<span class="title function_">push</span>(...ports);</span><br><span class="line">    <span class="keyword">if</span> (ports[<span class="number">0</span>].<span class="property">isOccupied</span>) &#123;</span><br><span class="line">        <span class="title function_">runInquirerCommand</span>();</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="title function_">execa</span>(<span class="string">&#x27;npm&#x27;</span>, [<span class="string">&#x27;run&#x27;</span>, <span class="string">&#x27;dev&#x27;</span>], &#123;</span><br><span class="line">            <span class="attr">cwd</span>: <span class="string">&#x27;./packages/base&#x27;</span>, </span><br><span class="line">            <span class="attr">stdio</span>: <span class="string">&#x27;inherit&#x27;</span></span><br><span class="line">        &#125;);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="comment">// scripts/build.js：运行指令后可多选要打包的应用，随后会按顺序逐个打包。</span></span><br><span class="line"><span class="keyword">const</span> inquirer = <span class="built_in">require</span>(<span class="string">&#x27;inquirer&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> execa = <span class="built_in">require</span>(<span class="string">&#x27;execa&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> config = <span class="built_in">require</span>(<span class="string">&#x27;../config.json&#x27;</span>);</span><br><span class="line">inquirer.<span class="title function_">prompt</span>([&#123;</span><br><span class="line">    <span class="attr">name</span>: <span class="string">&#x27;buildPackage&#x27;</span>,</span><br><span class="line">    <span class="attr">type</span>: <span class="string">&#x27;checkbox&#x27;</span>,</span><br><span class="line">    <span class="attr">message</span>: <span class="string">&#x27;请选择要打包的应用&#x27;</span>,</span><br><span class="line">    <span class="attr">choices</span>: <span class="title class_">Object</span>.<span class="title function_">keys</span>(config).<span class="title function_">map</span>(<span class="function"><span class="params">key</span> =&gt;</span> &#123;</span><br><span class="line">        <span class="keyword">const</span> &#123; name, packageName &#125; = config[key];</span><br><span class="line">        <span class="keyword">return</span> &#123; <span class="attr">name</span>: <span class="string">`<span class="subst">$&#123;name&#125;</span>(<span class="subst">$&#123;packageName&#125;</span>)`</span>, <span class="attr">value</span>: key &#125;;</span><br><span class="line">    &#125;)</span><br><span class="line">&#125;])</span><br><span class="line">.<span class="title function_">then</span>(<span class="keyword">async</span> (answer) =&gt; &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">time</span>(<span class="string">&#x27;打包完成，耗时&#x27;</span>);</span><br><span class="line">    <span class="title class_">Promise</span>.<span class="title function_">all</span>(answer.<span class="property">buildPackage</span>.<span class="title function_">map</span>(<span class="function"><span class="params">item</span> =&gt;</span> <span class="keyword">new</span> <span class="title class_">Promise</span>(<span class="function"><span class="params">resolve</span> =&gt;</span> &#123;</span><br><span class="line">        <span class="title function_">execa</span>(<span class="string">&#x27;npm&#x27;</span>, [<span class="string">&#x27;run&#x27;</span>, <span class="string">&#x27;build&#x27;</span>], &#123; </span><br><span class="line">            <span class="attr">cwd</span>: <span class="string">`./packages/<span class="subst">$&#123;item&#125;</span>`</span>,</span><br><span class="line">            <span class="attr">stdio</span>: <span class="string">&#x27;inherit&#x27;</span>,</span><br><span class="line">        &#125;).<span class="title function_">then</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">            <span class="title function_">resolve</span>();</span><br><span class="line">        &#125;);</span><br><span class="line">    &#125;))).<span class="title function_">then</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">        <span class="variable language_">console</span>.<span class="title function_">timeEnd</span>(<span class="string">&#x27;打包完成，耗时&#x27;</span>);</span><br><span class="line">    &#125;);</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 构建目录：可通过 webpack outputDir、vite build.outDir 将打包后的路径</span></span><br><span class="line"><span class="comment">// 改为根目录的 dist，然后根据基座或子应用放置到不同文件夹，最后结构应该如下：</span></span><br><span class="line">dist</span><br><span class="line">├── base   </span><br><span class="line">├── child</span><br><span class="line">|   ├── child-app1</span><br><span class="line">|   ├── child-app2</span><br><span class="line">|   ├── ...</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">// 2、Nginx 配置：将微前端子项目部署时产生的多个地址反向代理代理到主项目地址。</span></span><br><span class="line">server &#123;</span><br><span class="line">    listen       <span class="number">80</span>;</span><br><span class="line">    server_name  localhost;</span><br><span class="line"></span><br><span class="line">    gzip  on;  </span><br><span class="line">    gzip_min_length 1k;</span><br><span class="line">    gzip_comp_level <span class="number">5</span>; </span><br><span class="line">    gzip_types text/plain application/javascript application/x-</span><br><span class="line">    javascript text/javascript text/xml text/css;</span><br><span class="line">    gzip_disable <span class="string">&quot;MSIE [1-6]\.&quot;</span>;</span><br><span class="line">    gzip_vary on;</span><br><span class="line"></span><br><span class="line">    location / &#123;</span><br><span class="line">        root /usr/share/nginx/html/base;</span><br><span class="line">        index index.<span class="property">php</span> index.<span class="property">html</span> index.<span class="property">htm</span>;</span><br><span class="line">        # add_header <span class="title class_">Cache</span>-<span class="title class_">Control</span>;</span><br><span class="line">        add_header <span class="title class_">Access</span>-<span class="title class_">Control</span>-<span class="title class_">Allow</span>-<span class="title class_">Origin</span> *;</span><br><span class="line">        <span class="keyword">if</span> ( $request_uri ~* ^.+.(js|css|jpg|png|gif|tif|dpg|jpeg</span><br><span class="line">        |eot|svg|ttf|woff|json|mp4|rmvb|rm|wmv|avi|3gp)$ )&#123;</span><br><span class="line">            add_header <span class="title class_">Cache</span>-<span class="title class_">Control</span> max-age=<span class="number">7776000</span>;</span><br><span class="line">            add_header <span class="title class_">Access</span>-<span class="title class_">Control</span>-<span class="title class_">Allow</span>-<span class="title class_">Origin</span> *;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    location /base &#123;</span><br><span class="line">        root /usr/share/nginx/html;</span><br><span class="line">        add_header <span class="title class_">Access</span>-<span class="title class_">Control</span>-<span class="title class_">Allow</span>-<span class="title class_">Origin</span> *;</span><br><span class="line">        <span class="keyword">if</span> ( $request_uri ~* ^.+.(js|css|jpg|png|gif|tif|dpg|jpeg</span><br><span class="line">        |eot|svg|ttf|woff|json|mp4|rmvb|rm|wmv|avi|3gp)$ )&#123;</span><br><span class="line">            add_header <span class="title class_">Cache</span>-<span class="title class_">Control</span> max-age=<span class="number">7776000</span>;</span><br><span class="line">            add_header <span class="title class_">Access</span>-<span class="title class_">Control</span>-<span class="title class_">Allow</span>-<span class="title class_">Origin</span> *;</span><br><span class="line">        &#125;</span><br><span class="line">        try_files $uri $uri/ <span class="regexp">/base/i</span>ndex.<span class="property">html</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    location ^~ <span class="regexp">/child &#123;  /</span><span class="regexp">/ 匹配所有 child 目录子应用，增加时不用再配置</span></span><br><span class="line"><span class="regexp">        root /u</span>sr/share/nginx/html;</span><br><span class="line">        add_header <span class="title class_">Access</span>-<span class="title class_">Control</span>-<span class="title class_">Allow</span>-<span class="title class_">Origin</span> *;</span><br><span class="line">        <span class="keyword">if</span> ( $request_uri ~* ^.+.(js|css|jpg|png|gif|tif|dpg|jpeg</span><br><span class="line">        |eot|svg|ttf|woff|json|mp4|rmvb|rm|wmv|avi|3gp)$ )&#123;</span><br><span class="line">            add_header <span class="title class_">Cache</span>-<span class="title class_">Control</span> max-age=<span class="number">7776000</span>;</span><br><span class="line">            add_header <span class="title class_">Access</span>-<span class="title class_">Control</span>-<span class="title class_">Allow</span>-<span class="title class_">Origin</span> *;</span><br><span class="line">        &#125;</span><br><span class="line">        try_files $uri $uri/ index.<span class="property">html</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">// 3、Docker + Nginx 在本地模拟生产环境：创建和管理容器、组合可通信的应用</span></span><br><span class="line"><span class="comment">// Dockerfile：用于自动化构建映像的脚本文件，Docker 安装后即可在根目录新建</span></span><br><span class="line"><span class="variable constant_">FROM</span> <span class="attr">nginx</span>:latest                     <span class="comment">// 使用最新 nginx 作为基础镜像</span></span><br><span class="line"><span class="variable constant_">COPY</span> dist /usr/share/nginx/html       <span class="comment">// dist 本地复制到 nginx 目录</span></span><br><span class="line"><span class="variable constant_">COPY</span> nginx.<span class="property">conf</span> /usr/share/nginx/conf/nginx.<span class="property">conf</span>  <span class="comment">// 复制到配置目录</span></span><br><span class="line"><span class="variable constant_">EXPOSE</span> <span class="number">80</span></span><br><span class="line"><span class="variable constant_">CMD</span> [<span class="string">&quot;nginx&quot;</span>, <span class="string">&quot;-c&quot;</span>, <span class="string">&quot;/usr/share/nginx/conf/nginx.conf&quot;</span>, <span class="string">&quot;-g&quot;</span>, </span><br><span class="line"><span class="string">&quot;daemon off;&quot;</span>]   <span class="comment">// daemon off 关闭守护进程模式，调试时方便查看日志信息</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// docker-compose.yaml：为了方便定义和管理运行多个容器而在根目录新建</span></span><br><span class="line"><span class="attr">services</span>:        <span class="comment">// 定义服务 web 并通过 Dockerfile 构建镜像 web</span></span><br><span class="line">  <span class="attr">web</span>:</span><br><span class="line">    <span class="attr">build</span>:</span><br><span class="line">      <span class="attr">context</span>: .</span><br><span class="line">      <span class="attr">dockerfile</span>: <span class="title class_">Dockerfile</span></span><br><span class="line">    <span class="attr">image</span>: web</span><br><span class="line">    <span class="attr">container_name</span>: web  </span><br><span class="line">    <span class="attr">ports</span>:   </span><br><span class="line">      - <span class="number">80</span>:<span class="number">80</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 执行相关命令：启动服务、停止服务并删除容器、重新构建镜像并启动服务</span></span><br><span class="line">docker-compose up -d</span><br><span class="line">docker-compose down</span><br><span class="line">docker-compose up -d --build</span><br><span class="line"><span class="comment">// vscode Docker 插件：可查看容器和镜像的状态、管理容器/镜像和服务</span></span><br><span class="line"><span class="comment">// 生产环境部署：如果本机测试环境下正常启动，则生产环境的配置几乎一致</span></span><br></pre></td></tr></table></figure>


<h1 id="三、中间件"><a href="#三、中间件" class="headerlink" title="三、中间件"></a>三、中间件</h1><blockquote>
<p>一种用于连接操作系统、数据库等系统软件与应用软件，并管理计算资源和网络通信的中间软件。它为应用提供了开箱即用的业务服务和标准的通信接口，屏蔽了底层实现逻辑而让开发者专注于业务，解决了异构网络环境下分布式应用的通信问题，常作为服务器或插件被部署到应用开发工具。应用可以直接和用户交互但必须通过中间件和底层系统实现交互。</p>
</blockquote>
<h2 id="主要分类"><a href="#主要分类" class="headerlink" title="主要分类"></a>主要分类</h2><ul>
<li><strong>基础中间件</strong>：用于应用通信、请求处理、web 应用构建等。主要有<strong>交易中间件</strong> (用于银行业务等需要处理大量并发进程的联机交易系统)、<strong>消息中间件</strong> (用于应用之间的异步网络通信)、<strong>应用服务器中间件</strong> (用于浏览器和数据库之间业务处理的开发工具和运行平台)。</li>
<li><strong>集成中间件</strong>：用于数据库和应用等异构系统之间进行资源整合，构建一个分布式企业应用。</li>
<li><strong>行业领域应用平台</strong>：基于以上中间件针对特定行业开发文件管理、数据共享、云计算等功能。</li>
</ul>
<h2 id="常用实例"><a href="#常用实例" class="headerlink" title="常用实例"></a>常用实例</h2><ul>
<li><strong>路由与web服务器</strong>：处理和转发其他服务器数据的服务器。Nginx、Tengine、VipServer。</li>
<li><strong>RPC 框架</strong>：微服务时代的远程服务调用框架。grpc、Thrift、HSF、Dubbo、SOFA-RPC。</li>
<li><strong>消息中间件</strong>：分布式应用之间发送和接收消息的软件。Kafka、RabbitMQ、RocketMQ。</li>
<li><strong>缓存服务</strong>：分布式的高速数据存储层，一般是内存存储。Tair、Redis、Memcached。</li>
<li><strong>配置中心</strong>：统一管理各个项目中所有配置。阿里 Nacos、携程 Apollo、百度 Disconf。</li>
<li><strong>分布式事务</strong>：事务的参与者、支持事务的服务器、资源服务器以及事务管理器，分别位于不同的分布式系统的不同节点。阿里 seata、腾讯 DTF。</li>
<li><strong>任务调度</strong>：分布式环境下提供定时、任务编排、分布式跑批等功能的系统。阿里 SchedulerX、业界 xxl-job、当当 elastic-job、有赞 TSP。</li>
<li><strong>数据库层</strong>：支持弹性扩容和分库分表的 TDDL、数据库连接池 Driud、Binlog 同步的 Canal。</li>
</ul>
<h2 id="消息中间件"><a href="#消息中间件" class="headerlink" title="消息中间件"></a>消息中间件</h2><blockquote>
<p>缺点：系统可用性降低、系统稳定性降低、分布式一致性问题。</p>
</blockquote>
<ul>
<li><strong>复杂链路的异步调用</strong>：如果大量并发请求不需要服务器及时响应时则可将消息缓存后按顺序处理，比如用户注册(发布)、数据入库、消息队列、异步发送注册短信。</li>
<li><strong>复杂系统的解耦</strong>：将服务拆分，比如用户下单(发布)、消息队列、库存系统(订阅)处理业务。</li>
<li><strong>瞬时高峰的削峰处理</strong>：商品秒杀活动时过滤掉大多数无效请求后转发，比如用户下单(发送)、消息队列(根据实际情况限制人数等)、订阅消息根据秒杀规则处理业务。</li>
<li><strong>分布式应用的通信</strong>：根据应用使用的开发语言选择合适接口实现通信并对数据进行处理和转发，比如服务 A (发送消息)、消息队列、服务 B (订阅消息)。</li>
</ul>
<h2 id="JS-中间件"><a href="#JS-中间件" class="headerlink" title="JS 中间件"></a>JS 中间件</h2><blockquote>
<p>用于将业务逻辑拼接成一个逻辑处理链条的<code>洋葱圈模型</code>，每一个中间件的执行结果会传递到下一个中间件并等待其结束以后又回到当前执行逻辑，缺点主要有系统可用性和稳定性降低、分布式一致性问题。主要用于实现复杂系统的 <code>切面编程</code> (将函数插入到其它函数的前后)。</p>
</blockquote>
<ul>
<li><strong>axios</strong>：最简单，直接 promise 链式调用。</li>
<li><strong>redux</strong>：通过 reduce 静态生成，将中间件的执行结果作为上一个中间件的 next 参数。</li>
<li><strong>express、Redux</strong>：next 通过 redux 静态生成并已确定执行顺序，express 则是动态执行。</li>
<li><strong>express、koa</strong>：通过递归动态执行，区别在于 koa 通过 return promise 形成洋葱模型，expres 则在调用时没有 return，最终执行的是 await undefind 而不是 await promise。</li>
<li><strong>compose 为什么必须返回 promise.resolve</strong>：防止只有请求中间件且为同步函数时报错。</li>
<li><strong>redux 中间件必须返回函数</strong>：redux 多了一个组合的过程，组合时需要将中间件的执行结果而不是函数来作为上一个中间件的 next 参数，否则传给中间件就会报错。</li>
</ul>
  <figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 切面编程：将事前处理、事后处理、业务逻辑编成了执行链条</span></span><br><span class="line">@<span class="title class_">Before</span>(tokenCheck)       <span class="comment">// 鉴权</span></span><br><span class="line">@<span class="title class_">Before</span>(tx.<span class="property">begin</span>)         <span class="comment">// 开启事务</span></span><br><span class="line">@<span class="title class_">After</span>(log)               <span class="comment">// 操作日志</span></span><br><span class="line">@<span class="title class_">After</span>(tx.<span class="property">end</span>)            <span class="comment">// 关闭事务</span></span><br><span class="line">@<span class="title class_">Exception</span>(log)           <span class="comment">// 错误日志</span></span><br><span class="line">@<span class="title class_">Exception</span>(tx.<span class="property">rollback</span>)   <span class="comment">// 回滚事务</span></span><br><span class="line"><span class="title function_">traansferBase</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="comment">// 业务逻辑</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="title function_">tokenCheck</span>()</span><br><span class="line">tx.<span class="title function_">begin</span>()</span><br><span class="line"><span class="keyword">try</span> &#123;</span><br><span class="line">    <span class="comment">// 业务逻辑</span></span><br><span class="line">    <span class="title function_">log</span>(<span class="string">&#x27;操作日志&#x27;</span>)</span><br><span class="line">&#125; <span class="title function_">cache</span>(<span class="params">err</span>) &#123;</span><br><span class="line">    <span class="title function_">log</span>(<span class="string">&#x27;错误日志&#x27;</span>)</span><br><span class="line">    tx.<span class="property">rollback</span></span><br><span class="line">&#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">    tx.<span class="property">end</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">// 函数处理前执行 fn</span></span><br><span class="line"><span class="title class_">Function</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">before</span> = <span class="keyword">function</span>(<span class="params">fn</span>)&#123;</span><br><span class="line">    <span class="keyword">var</span> self = <span class="variable language_">this</span>;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">function</span>(<span class="params"></span>)&#123;</span><br><span class="line">        <span class="keyword">var</span> res = fn.<span class="title function_">call</span>(<span class="variable language_">this</span>)</span><br><span class="line">        <span class="keyword">if</span> (!res) &#123; <span class="keyword">return</span> &#125;</span><br><span class="line">        self.<span class="title function_">apply</span>(<span class="variable language_">this</span>, <span class="variable language_">arguments</span>)</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 函数处理后执行 fn</span></span><br><span class="line"><span class="title class_">Function</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">after</span> = <span class="keyword">function</span>(<span class="params">fn</span>)&#123;</span><br><span class="line">    <span class="keyword">var</span> self = <span class="variable language_">this</span>;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">function</span>(<span class="params"></span>)&#123;</span><br><span class="line">        self.<span class="title function_">apply</span>(<span class="variable language_">this</span>, <span class="variable language_">arguments</span>);</span><br><span class="line">        fn.<span class="title function_">call</span>(<span class="variable language_">this</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 提交前上报数据、验证数据</span></span><br><span class="line">submit.<span class="title function_">before</span>(report).<span class="title function_">before</span>(validate)()</span><br><span class="line"><span class="keyword">function</span> <span class="title function_">submit</span>(<span class="params"></span>)&#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;提交数据&#x27;</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

  <figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Axios</span></span><br><span class="line"><span class="comment">// 使用中间件：成功回调、失败回调、是否同步执行中间件 request、运行时是否添加中间件</span></span><br><span class="line"><span class="title class_">InterceptorManager</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">use</span> = <span class="keyword">function</span> (<span class="params">fulfilled, rejected, options</span>) &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">handlers</span>.<span class="title function_">push</span>(&#123;</span><br><span class="line">        <span class="attr">fulfilled</span>: fulfilled,</span><br><span class="line">        <span class="attr">rejected</span>: rejected,</span><br><span class="line">        <span class="attr">runWhen</span>: options ? options.<span class="property">runWhen</span> : <span class="literal">null</span>,</span><br><span class="line">        <span class="attr">synchronous</span>: options ? options.<span class="property">synchronous</span> : <span class="literal">false</span></span><br><span class="line">    &#125;);</span><br><span class="line">    <span class="keyword">return</span> <span class="variable language_">this</span>.<span class="property">handlers</span>.<span class="property">length</span> - <span class="number">1</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 添加中间件：adapter 是 node/browser 请求的适配器</span></span><br><span class="line"><span class="variable language_">module</span>.<span class="property">exports</span> = <span class="keyword">function</span> <span class="title function_">dispatchRequest</span>(<span class="params">config</span>) &#123;</span><br><span class="line">    <span class="keyword">var</span> adapter = config.<span class="property">adapter</span> || defaults.<span class="property">adapter</span>;</span><br><span class="line">    <span class="keyword">return</span> <span class="title function_">adapter</span>(config).<span class="title function_">then</span>(<span class="keyword">function</span> <span class="title function_">onAdapterResolution</span>(<span class="params">response</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> response;</span><br><span class="line">    &#125;, <span class="keyword">function</span> <span class="title function_">onAdapterRejection</span>(<span class="params">reason</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="title class_">Promise</span>.<span class="title function_">reject</span>(reason);</span><br><span class="line">    &#125;);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 1、定义 request、response 拦截器并插在 dispatchRequest 前后</span></span><br><span class="line"><span class="title class_">Axios</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">request</span> = <span class="keyword">function</span> <span class="title function_">request</span>(<span class="params">config</span>) &#123;</span><br><span class="line">  <span class="keyword">var</span> requestInterceptorChain = [];</span><br><span class="line">  <span class="keyword">var</span> synchronousRequestInterceptors = <span class="literal">true</span>;</span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">interceptors</span>.<span class="property">request</span>.<span class="title function_">forEach</span>(<span class="keyword">function</span> <span class="title function_">unshiftRequestInterceptors</span>(<span class="params">interceptor</span>) &#123;</span><br><span class="line">      <span class="keyword">if</span> (<span class="keyword">typeof</span> interceptor.<span class="property">runWhen</span> === <span class="string">&#x27;function&#x27;</span> &amp;&amp; </span><br><span class="line">      interceptor.<span class="title function_">runWhen</span>(config) === <span class="literal">false</span>) &#123; </span><br><span class="line">          <span class="keyword">return</span>; </span><br><span class="line">      &#125;</span><br><span class="line"></span><br><span class="line">      synchronousRequestInterceptors = synchronousRequestInterceptors &amp;&amp; </span><br><span class="line">      interceptor.<span class="property">synchronous</span>;</span><br><span class="line">      requestInterceptorChain.<span class="title function_">unshift</span>(interceptor.<span class="property">fulfilled</span>, interceptor.<span class="property">rejected</span>);</span><br><span class="line">  &#125;);</span><br><span class="line"></span><br><span class="line">  <span class="keyword">var</span> responseInterceptorChain = [];</span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">interceptors</span>.<span class="property">response</span>.<span class="title function_">forEach</span>(<span class="keyword">function</span> <span class="title function_">pushResponseInterceptors</span>(<span class="params">interceptor</span>) &#123;</span><br><span class="line">      responseInterceptorChain.<span class="title function_">push</span>(interceptor.<span class="property">fulfilled</span>, interceptor.<span class="property">rejected</span>);</span><br><span class="line">  &#125;);</span><br><span class="line"></span><br><span class="line">  <span class="keyword">var</span> promise;</span><br><span class="line">  <span class="comment">// 2、request 都是异步，构建链式调用 then(fulfilled, rejected).then(fulfilled, rejected)</span></span><br><span class="line">  <span class="keyword">if</span> (!synchronousRequestInterceptors) &#123;</span><br><span class="line">      <span class="keyword">var</span> chain = [dispatchRequest, <span class="literal">undefined</span>];</span><br><span class="line"></span><br><span class="line">      <span class="title class_">Array</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">unshift</span>.<span class="title function_">apply</span>(chain, requestInterceptorChain);</span><br><span class="line">      chain.<span class="title function_">concat</span>(responseInterceptorChain);</span><br><span class="line"></span><br><span class="line">      promise = <span class="title class_">Promise</span>.<span class="title function_">resolve</span>(config);</span><br><span class="line">      <span class="keyword">while</span> (chain.<span class="property">length</span>) &#123;</span><br><span class="line">          promise = promise.<span class="title function_">then</span>(chain.<span class="title function_">shift</span>(), chain.<span class="title function_">shift</span>());</span><br><span class="line">      &#125;</span><br><span class="line"></span><br><span class="line">      <span class="keyword">return</span> promise;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 3、如果存在 request 同步拦截器则先执行并获取新配置，然后构建链式调用 promise.then.then</span></span><br><span class="line">  <span class="keyword">var</span> newConfig = config;</span><br><span class="line">  <span class="keyword">while</span> (requestInterceptorChain.<span class="property">length</span>) &#123;</span><br><span class="line">      <span class="keyword">var</span> onFulfilled = requestInterceptorChain.<span class="title function_">shift</span>();</span><br><span class="line">      <span class="keyword">var</span> onRejected = requestInterceptorChain.<span class="title function_">shift</span>();</span><br><span class="line">      <span class="keyword">try</span> &#123;</span><br><span class="line">          newConfig = <span class="title function_">onFulfilled</span>(newConfig);</span><br><span class="line">      &#125; <span class="keyword">catch</span> (error) &#123;</span><br><span class="line">          <span class="title function_">onRejected</span>(error);</span><br><span class="line">          <span class="keyword">break</span>;</span><br><span class="line">      &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">try</span> &#123;</span><br><span class="line">      promise = <span class="title function_">dispatchRequest</span>(newConfig);</span><br><span class="line">  &#125; <span class="keyword">catch</span> (error) &#123;</span><br><span class="line">      <span class="keyword">return</span> <span class="title class_">Promise</span>.<span class="title function_">reject</span>(error);</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">while</span> (responseInterceptorChain.<span class="property">length</span>) &#123;</span><br><span class="line">      promise = promise.<span class="title function_">then</span>(responseInterceptorChain.<span class="title function_">shift</span>(), </span><br><span class="line">      responseInterceptorChain.<span class="title function_">shift</span>());</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> promise;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

  <figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Redux：将中间件执行结果作为上一个中间件的 next 参数，类似 pipe 反向装载和正向执行</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">compose</span>(<span class="params">...funcs</span>) &#123;</span><br><span class="line">    <span class="keyword">if</span> (funcs.<span class="property">length</span> === <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="function"><span class="params">arg</span> =&gt;</span> arg;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (funcs.<span class="property">length</span> === <span class="number">1</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> funcs[<span class="number">0</span>];</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> funcs.<span class="title function_">reduce</span>(<span class="function">(<span class="params">a, b</span>) =&gt;</span> <span class="function">(<span class="params">...args</span>) =&gt;</span> <span class="title function_">a</span>(<span class="title function_">b</span>(...args)));</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">// 举例实现：依次执行中间件 add multiple、目标函数 print</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">add</span>(<span class="params">next</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="function">(<span class="params">num</span>) =&gt;</span> &#123; <span class="title function_">next</span>(num + <span class="number">1</span>) &#125;;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">function</span> <span class="title function_">multiple</span>(<span class="params">next</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="function">(<span class="params">num</span>) =&gt;</span> &#123;</span><br><span class="line">        <span class="built_in">setTimeout</span>(<span class="function">() =&gt;</span> &#123; <span class="title function_">next</span>(num * <span class="number">2</span>) &#125;, <span class="number">2000</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">function</span> <span class="title function_">print</span>(<span class="params">num</span>)&#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(num)</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">const</span> compute = <span class="title function_">compose</span>(add, multiple)(print) = <span class="title function_">add</span>(<span class="title function_">mulitple</span>(print))</span><br><span class="line"><span class="title function_">compute</span>(<span class="number">1</span>)</span><br></pre></td></tr></table></figure>

  <figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@title</span> <span class="variable">Express</span></span></span><br><span class="line"><span class="comment"> * </span></span><br><span class="line"><span class="comment"> * <span class="doctag">@prame</span> app：管理多个中间件的实例</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@prame</span> funcs：存储待执行的函数数组</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@preme</span> app.use：将函数添加到函数数组的全局中间件</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@preme</span> next：递归执行到最后一个任务 task[0]()、task[1]()</span></span><br><span class="line"><span class="comment">**/</span> </span><br><span class="line"><span class="keyword">function</span> <span class="title function_">express</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">var</span> funcs = [];</span><br><span class="line">    <span class="keyword">var</span> app = <span class="keyword">function</span> (<span class="params">req, res</span>) &#123;</span><br><span class="line">        <span class="keyword">var</span> i = <span class="number">0</span>;</span><br><span class="line">        <span class="keyword">function</span> <span class="title function_">next</span>(<span class="params"></span>) &#123;</span><br><span class="line">            <span class="keyword">var</span> task = funcs[i++]; <span class="comment">// 取出函数数组里的下一个函数</span></span><br><span class="line">            <span class="keyword">if</span> (!task) &#123; <span class="keyword">return</span> &#125;  <span class="comment">// 如果函数不存在则执行 return</span></span><br><span class="line">            <span class="title function_">task</span>(req, res, next);  <span class="comment">// 否则执行下个函数，没有 return</span></span><br><span class="line">        &#125;</span><br><span class="line">        <span class="title function_">next</span>();</span><br><span class="line">    &#125;;</span><br><span class="line">    app.<span class="property">use</span> = <span class="keyword">function</span> (<span class="params">task</span>) &#123;</span><br><span class="line">        funcs.<span class="title function_">push</span>(task);</span><br><span class="line">    &#125;;</span><br><span class="line">    <span class="keyword">return</span> app;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> express = <span class="built_in">require</span>(<span class="string">&#x27;express&#x27;</span>);</span><br><span class="line"><span class="keyword">var</span> app = <span class="title function_">express</span>();</span><br><span class="line">app.<span class="title function_">use</span>(<span class="keyword">function</span>(<span class="params">req, res, next</span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;数据统计&#x27;</span>);</span><br><span class="line">    <span class="title function_">next</span>();</span><br><span class="line">&#125;);</span><br><span class="line">app.<span class="title function_">use</span>(<span class="keyword">function</span>(<span class="params">req, res, next</span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;日志统计&#x27;</span>);</span><br><span class="line">    <span class="title function_">next</span>();</span><br><span class="line">&#125;);</span><br><span class="line">app.<span class="title function_">get</span>(<span class="string">&#x27;/&#x27;</span>, <span class="keyword">function</span>(<span class="params">req, res, next</span>) &#123;</span><br><span class="line">    res.<span class="title function_">send</span>(<span class="string">&#x27;Hello World!&#x27;</span>);</span><br><span class="line">&#125;);</span><br><span class="line">app.<span class="title function_">listen</span>(<span class="number">3000</span>);</span><br></pre></td></tr></table></figure>

  <figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// koa-compose</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">compose</span> (middleware) &#123;</span><br><span class="line">    <span class="keyword">if</span> (!<span class="title class_">Array</span>.<span class="title function_">isArray</span>(middleware)) &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">TypeError</span>(<span class="string">&#x27;Middleware stack must be an array!&#x27;</span>)</span><br><span class="line">    &#125; </span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">const</span> fn <span class="keyword">of</span> middleware) &#123;</span><br><span class="line">        <span class="keyword">if</span> (<span class="keyword">typeof</span> fn !== <span class="string">&#x27;function&#x27;</span>) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">TypeError</span>(<span class="string">&#x27;Middleware must be composed!&#x27;</span>)</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">function</span> (<span class="params">cxt, next</span>) &#123;</span><br><span class="line">        <span class="keyword">let</span> index = -<span class="number">1</span></span><br><span class="line">        <span class="keyword">let</span> newError = <span class="keyword">new</span> <span class="title class_">Error</span>(<span class="string">&#x27;next() called multiple times&#x27;</span>)</span><br><span class="line">        <span class="keyword">return</span> <span class="title function_">dispatch</span>(<span class="number">0</span>)</span><br><span class="line">        <span class="keyword">function</span> <span class="title function_">dispatch</span> (i) &#123;</span><br><span class="line">            <span class="keyword">if</span> (i &lt;= index) <span class="keyword">return</span> <span class="title class_">Promise</span>.<span class="title function_">reject</span>(newError)</span><br><span class="line">            index = i</span><br><span class="line">            <span class="keyword">let</span> fn = middleware[i]</span><br><span class="line">            <span class="comment">// 洋葱类型：中间件正常执行完则赋值为外部传入的目标函数</span></span><br><span class="line">            <span class="keyword">if</span> (i === middleware.<span class="property">length</span>) fn = next</span><br><span class="line">            <span class="comment">// 终止条件，返回 promise</span></span><br><span class="line">            <span class="keyword">if</span> (!fn) <span class="keyword">return</span> <span class="title class_">Promise</span>.<span class="title function_">resolve</span>()</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">              <span class="comment">// 递归返回：确保 fn() 返回 promise，并修改中间件的 next 参数</span></span><br><span class="line">              <span class="keyword">return</span> <span class="title class_">Promise</span>.<span class="title function_">resolve</span>(<span class="title function_">fn</span>(cxt, dispatch.<span class="title function_">bind</span>(<span class="literal">null</span>, i+<span class="number">1</span>)))</span><br><span class="line">            &#125; <span class="keyword">catch</span> (err) &#123;</span><br><span class="line">              <span class="keyword">return</span> <span class="title class_">Promise</span>.<span class="title function_">reject</span>(err)</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 运行结果：1、2、3、4、5</span></span><br><span class="line"><span class="keyword">import</span> compose <span class="keyword">from</span> <span class="string">&#x27;koa-compose&#x27;</span>;</span><br><span class="line"><span class="keyword">async</span> <span class="title function_">m1</span>(<span class="params">ctx, next</span>)&#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="number">1</span>);</span><br><span class="line">    <span class="keyword">await</span> <span class="title function_">next</span>();</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="number">5</span>);</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">async</span> <span class="title function_">m2</span>(<span class="params">ctx , next</span>)&#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="number">2</span>);</span><br><span class="line">    <span class="keyword">await</span> <span class="title function_">next</span>();</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="number">4</span>);</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">const</span> c = <span class="title function_">compose</span>([m1, m2])</span><br><span class="line"><span class="title function_">c</span>(<span class="literal">null</span>, <span class="function">()=&gt;</span>&#123; <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="number">3</span>) &#125;)</span><br><span class="line"></span><br><span class="line"><span class="comment">// 代码转换</span></span><br><span class="line"><span class="title class_">Promise</span>.<span class="title function_">resolve</span>(<span class="title function_">m1</span>(context, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="title class_">Promise</span>.<span class="title function_">resolve</span>(<span class="title function_">m2</span>(ctx, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="title class_">Promise</span>.<span class="title function_">resolve</span>(<span class="title function_">next</span>(ctx, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="title class_">Promise</span>.<span class="title function_">resolve</span>();</span><br><span class="line">        &#125;))</span><br><span class="line">    &#125;))</span><br><span class="line">&#125;))</span><br><span class="line"><span class="title class_">Promise</span>.<span class="title function_">resolve</span>((<span class="keyword">async</span> () =&gt; &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="number">1</span>);</span><br><span class="line">    <span class="keyword">await</span> <span class="title class_">Promise</span>.<span class="title function_">resolve</span>(</span><br><span class="line">        (<span class="keyword">async</span> () =&gt; &#123;</span><br><span class="line">            <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="number">2</span>);</span><br><span class="line">            <span class="keyword">await</span> <span class="title class_">Promise</span>.<span class="title function_">resolve</span>((<span class="function">() =&gt;</span> &#123; <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="number">3</span>) &#125;)())</span><br><span class="line">            <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="number">4</span>)</span><br><span class="line">        &#125;)()</span><br><span class="line">    )</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="number">5</span>)</span><br><span class="line">&#125;)())</span><br></pre></td></tr></table></figure>

<h1 id="四、NodeJS-微服务"><a href="#四、NodeJS-微服务" class="headerlink" title="四、NodeJS 微服务"></a>四、NodeJS 微服务</h1><blockquote>
<p>构建的主要优势有：<strong>单线程+异步</strong> (通过事件循环来执行代码而实现非阻塞机制)、<strong>事件驱动</strong> (适用于实时应用的发布订阅或观察者模式)、<strong>快速和高度的可扩展性</strong> (运行环境基于引擎 V8 JavaScript Engine 而支持快速执行和处理大量并发请求)、<strong>易于开发</strong> NodeJS 框架抽象了大部底层系统而可以轻松创建微服务而不会导致大量重复代码。</p>
</blockquote>
<h2 id="构建项目"><a href="#构建项目" class="headerlink" title="构建项目"></a>构建项目</h2>  <figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// NestJS 框架构建</span></span><br><span class="line">cnpm i @nestjs/microservices --save</span><br><span class="line">npx @nestjs/cli <span class="keyword">new</span> user-microservice</span><br><span class="line"></span><br><span class="line"><span class="comment">// main.ts 配置：通过 TCP 传输层绑定到本地 8875 端口进行通信。</span></span><br><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">INestMicroservice</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;@nestjs/common&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">NestFactory</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;@nestjs/core&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">Transport</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;@nestjs/microservices&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">AppModule</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;./app.module&#x27;</span>;</span><br><span class="line"><span class="keyword">async</span> <span class="keyword">function</span> <span class="title function_">bootstrap</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">const</span> <span class="attr">microservicesOptions</span>: any = &#123;</span><br><span class="line">        <span class="attr">transport</span>: <span class="title class_">Transport</span>.<span class="property">TCP</span>,</span><br><span class="line">        <span class="attr">options</span>: &#123; <span class="attr">host</span>: <span class="string">&#x27;127.0.0.1&#x27;</span>, <span class="attr">port</span>: <span class="number">8875</span> &#125;</span><br><span class="line">    &#125;;</span><br><span class="line">    <span class="keyword">const</span> <span class="attr">app</span>: <span class="title class_">INestMicroservice</span> = <span class="keyword">await</span> <span class="title class_">NestFactory</span>.<span class="title function_">createMicroservice</span>(</span><br><span class="line">        <span class="title class_">AppModule</span>,</span><br><span class="line">        microservicesOptions</span><br><span class="line">    );</span><br><span class="line">    app.<span class="title function_">listen</span>(<span class="function">() =&gt;</span> <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;Microservice is Listening&#x27;</span>));</span><br><span class="line">&#125;</span><br><span class="line"><span class="title function_">bootstrap</span>();</span><br></pre></td></tr></table></figure>


<h2 id="监听消息"><a href="#监听消息" class="headerlink" title="监听消息"></a>监听消息</h2>  <figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// app.controller.ts：注册一个在服务之间通信的消息模式 (不用响应则用事件模式)。</span></span><br><span class="line">@<span class="title class_">Controller</span>()</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">class</span> <span class="title class_">AppController</span> &#123;</span><br><span class="line">    <span class="title function_">constructor</span>(<span class="params">private readonly appService: AppService</span>) &#123;&#125;</span><br><span class="line"></span><br><span class="line">    @<span class="title class_">MessagePattern</span>(<span class="string">&#x27;create_user&#x27;</span>)</span><br><span class="line">    <span class="keyword">async</span> <span class="title function_">createUser</span>(<span class="params">@Payload() payload: CreateUserDto</span>) &#123;</span><br><span class="line">        <span class="keyword">const</span> user = <span class="keyword">await</span> <span class="variable language_">this</span>.<span class="property">appService</span>.<span class="title function_">createUser</span>(payload);</span><br><span class="line">        <span class="keyword">return</span> user;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 创建一个新用户的负载格式：包含 email、password 的简单对象。</span></span><br><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">IsString</span>, <span class="title class_">IsEmail</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;class-validator&#x27;</span>;</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">class</span> <span class="title class_">CreateUserDto</span> &#123;</span><br><span class="line">    @<span class="title class_">IsEmail</span>()</span><br><span class="line">    <span class="attr">email</span>: string;</span><br><span class="line">  </span><br><span class="line">    @<span class="title class_">IsString</span>()</span><br><span class="line">    <span class="attr">password</span>: string;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>


<h2 id="测试服务"><a href="#测试服务" class="headerlink" title="测试服务"></a>测试服务</h2>  <figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@title</span> PacketSender：支持发送和接收 TCP/UDP 数据包的测试软件</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> 设置：127.0.0.1:8875、右侧菜单 TCP、ASCII 编码信息</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> 信息：pattern 寻找信息、data 发送对象、122 消息长度</span></span><br><span class="line"><span class="comment">**/</span> </span><br><span class="line"><span class="number">122</span>#&#123;<span class="string">&quot;pattern&quot;</span>:<span class="string">&quot;create_user&quot;</span>,</span><br><span class="line"><span class="string">&quot;data&quot;</span>:&#123;<span class="string">&quot;email&quot;</span>:<span class="string">&quot;d@gmail.com&quot;</span>,<span class="string">&quot;password&quot;</span>:<span class="string">&quot;12345678&quot;</span>&#125;,</span><br><span class="line"><span class="string">&quot;id&quot;</span>:<span class="string">&quot;ce51ebd3-32b1-4ae6-b7ef-e018126c4cc4&quot;</span>&#125;</span><br></pre></td></tr></table></figure>


<h2 id="API-网关"><a href="#API-网关" class="headerlink" title="API 网关"></a>API 网关</h2>  <figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// app.module.ts：相同步骤创建一个新应用并更新其配置。</span></span><br><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">Module</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;@nestjs/common&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">AppController</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;./app.controller&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">AppService</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;./app.service&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">ConfigService</span> &#125; <span class="keyword">from</span> <span class="string">&quot;./config/config.service&quot;</span>;</span><br><span class="line">@<span class="title class_">Module</span>(&#123;</span><br><span class="line">  <span class="attr">imports</span>: [],</span><br><span class="line">  <span class="attr">controllers</span>: [<span class="title class_">AppController</span>],</span><br><span class="line">  <span class="attr">providers</span>: [</span><br><span class="line">    &#123;</span><br><span class="line">      <span class="attr">provide</span>: <span class="string">&#x27;USER_MICROSERVICE&#x27;</span>,</span><br><span class="line">      <span class="attr">useFactory</span>: <span class="function">(<span class="params">configService: ConfigService</span>) =&gt;</span> &#123;</span><br><span class="line">          <span class="keyword">const</span> options = &#123;</span><br><span class="line">            <span class="attr">transport</span>: <span class="title class_">Transport</span>.<span class="property">TCP</span>,</span><br><span class="line">            <span class="attr">options</span>: &#123;</span><br><span class="line">              <span class="attr">host</span>: configService.<span class="title function_">get</span>(<span class="string">&#x27;USERS_MICROSERVICE_HOST&#x27;</span>),</span><br><span class="line">              <span class="attr">port</span>: <span class="title class_">Number</span>(configService.<span class="title function_">get</span>(<span class="string">&#x27;USERS_MICROSERVICE_PORT&#x27;</span>))</span><br><span class="line">            &#125;</span><br><span class="line">          &#125;;</span><br><span class="line">          <span class="keyword">return</span> <span class="title class_">ClientProxyFactory</span>.<span class="title function_">create</span>(options <span class="keyword">as</span> <span class="title class_">ClientOptions</span>);</span><br><span class="line">      &#125;,</span><br><span class="line">      <span class="attr">inject</span>: [<span class="title class_">ConfigService</span>]</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="title class_">AppService</span></span><br><span class="line">  ]</span><br><span class="line">&#125;)</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">class</span> <span class="title class_">AppModule</span> &#123;&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// AppController/AppService：注入一个链接对象来代表用户和微服务连接。</span></span><br><span class="line"><span class="comment">// API 网关会将 create-user 路由收到的请求和有效负载转发给微服务并获取响应给用户。</span></span><br><span class="line">@<span class="title class_">Controller</span>()</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">class</span> <span class="title class_">AppController</span> &#123;</span><br><span class="line">  <span class="title function_">constructor</span>(<span class="params"></span></span><br><span class="line"><span class="params">      @Inject(<span class="string">&#x27;USER_MICROSERVICE&#x27;</span>) private readonly client: ClientProxy,</span></span><br><span class="line"><span class="params">      private readonly appService: AppService</span></span><br><span class="line"><span class="params">  </span>) &#123;&#125;</span><br><span class="line"></span><br><span class="line">  @<span class="title class_">Post</span>(<span class="string">&#x27;create-user&#x27;</span>)</span><br><span class="line">  <span class="keyword">async</span> <span class="title function_">createUser</span>(<span class="params">@Body() payload: CreateUserDto</span>) &#123;</span><br><span class="line">      <span class="keyword">return</span> <span class="variable language_">this</span>.<span class="property">client</span>.<span class="title function_">send</span>(<span class="string">&#x27;create_user&#x27;</span>, payload).<span class="title function_">toPromise</span>();</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>




    </div>

    
    
    <div>
      
        <div>
    
        <div style="text-align:center;color: #ccc;font-size:14px;margin-top:20px;">-------------本文结束<i class="fa fa-paw"></i>感谢您的阅读-------------</div>
    
</div>

      
    </div>
    
        

<div>
<ul class="post-copyright">
  <li class="post-copyright-author">
    <strong>本文作者： </strong>chuangges
  </li>
  <li class="post-copyright-link">
    <strong>本文链接：</strong>
    <a href="https://chuangges.gitee.io/2022/11/10/18-%E5%BE%AE%E6%9C%8D%E5%8A%A1/" title="微服务技术栈">https://chuangges.gitee.io/2022/11/10/18-微服务/</a>
  </li>
  <li class="post-copyright-license">
    <strong>版权声明： </strong>本博客所有文章除特别声明外，均采用 <a href="https://creativecommons.org/licenses/by-nc-sa/4.0/zh_CN" rel="external nofollow noopener noreferrer" target="_blank"><i class="fab fa-fw fa-creative-commons"></i>BY-NC-SA</a> 许可协议。转载请注明出处！
  </li>
</ul>
</div>


      <footer class="post-footer">

        


        
    <div class="post-nav">
      <div class="post-nav-item">
    <a href="/2022/10/21/17-%E5%89%8D%E7%AB%AF%E6%8A%80%E8%83%BD/" rel="prev" title="前端应该知道的技能栈">
      <i class="fa fa-chevron-left"></i> 前端应该知道的技能栈
    </a></div>
      <div class="post-nav-item">
    <a href="/2023/01/10/19-%E7%99%BE%E5%BA%A6%E8%AF%86%E5%88%AB/" rel="next" title="百度智能云之在线识别">
      百度智能云之在线识别 <i class="fa fa-chevron-right"></i>
    </a></div>
    </div>
      </footer>
    
  </article>
  
  
  



          </div>
          

<script>
  window.addEventListener('tabs:register', () => {
    let { activeClass } = CONFIG.comments;
    if (CONFIG.comments.storage) {
      activeClass = localStorage.getItem('comments_active') || activeClass;
    }
    if (activeClass) {
      let activeTab = document.querySelector(`a[href="#comment-${activeClass}"]`);
      if (activeTab) {
        activeTab.click();
      }
    }
  });
  if (CONFIG.comments.storage) {
    window.addEventListener('tabs:click', event => {
      if (!event.target.matches('.tabs-comment .tab-content .tab-pane')) return;
      let commentClass = event.target.classList[1];
      localStorage.setItem('comments_active', commentClass);
    });
  }
</script>

        </div>
          
  
  <div class="toggle sidebar-toggle">
    <span class="toggle-line toggle-line-first"></span>
    <span class="toggle-line toggle-line-middle"></span>
    <span class="toggle-line toggle-line-last"></span>
  </div>

  <aside class="sidebar">
    <div class="sidebar-inner">

      <ul class="sidebar-nav motion-element">
        <li class="sidebar-nav-toc">
          文章目录
        </li>
        <li class="sidebar-nav-overview">
          站点概览
        </li>
      </ul>

      <!--noindex-->
      <div class="post-toc-wrap sidebar-panel">
          <div class="post-toc motion-element"><ol class="nav"><li class="nav-item nav-level-1"><a class="nav-link" href="#%E4%B8%80%E3%80%81%E5%BE%AE%E6%9C%8D%E5%8A%A1"><span class="nav-number">1.</span> <span class="nav-text">一、微服务</span></a><ol class="nav-child"><li class="nav-item nav-level-2"><a class="nav-link" href="#%E7%B3%BB%E7%BB%9F%E6%9E%B6%E6%9E%84"><span class="nav-number">1.1.</span> <span class="nav-text">系统架构</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E5%BC%80%E5%8F%91%E6%8A%80%E6%9C%AF"><span class="nav-number">1.2.</span> <span class="nav-text">开发技术</span></a></li></ol></li><li class="nav-item nav-level-1"><a class="nav-link" href="#%E4%BA%8C%E3%80%81%E5%BE%AE%E5%89%8D%E7%AB%AF"><span class="nav-number">2.</span> <span class="nav-text">二、微前端</span></a><ol class="nav-child"><li class="nav-item nav-level-2"><a class="nav-link" href="#%E6%9E%B6%E6%9E%84%E8%AE%BE%E8%AE%A1"><span class="nav-number">2.1.</span> <span class="nav-text">架构设计</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E5%BC%80%E5%8F%91%E6%8A%80%E6%9C%AF-1"><span class="nav-number">2.2.</span> <span class="nav-text">开发技术</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E6%8A%80%E6%9C%AF%E5%AE%9E%E6%88%98"><span class="nav-number">2.3.</span> <span class="nav-text">技术实战</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E5%BC%80%E5%8F%91%E6%9E%84%E5%BB%BA"><span class="nav-number">2.4.</span> <span class="nav-text">开发构建</span></a></li></ol></li><li class="nav-item nav-level-1"><a class="nav-link" href="#%E4%B8%89%E3%80%81%E4%B8%AD%E9%97%B4%E4%BB%B6"><span class="nav-number">3.</span> <span class="nav-text">三、中间件</span></a><ol class="nav-child"><li class="nav-item nav-level-2"><a class="nav-link" href="#%E4%B8%BB%E8%A6%81%E5%88%86%E7%B1%BB"><span class="nav-number">3.1.</span> <span class="nav-text">主要分类</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E5%B8%B8%E7%94%A8%E5%AE%9E%E4%BE%8B"><span class="nav-number">3.2.</span> <span class="nav-text">常用实例</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E6%B6%88%E6%81%AF%E4%B8%AD%E9%97%B4%E4%BB%B6"><span class="nav-number">3.3.</span> <span class="nav-text">消息中间件</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#JS-%E4%B8%AD%E9%97%B4%E4%BB%B6"><span class="nav-number">3.4.</span> <span class="nav-text">JS 中间件</span></a></li></ol></li><li class="nav-item nav-level-1"><a class="nav-link" href="#%E5%9B%9B%E3%80%81NodeJS-%E5%BE%AE%E6%9C%8D%E5%8A%A1"><span class="nav-number">4.</span> <span class="nav-text">四、NodeJS 微服务</span></a><ol class="nav-child"><li class="nav-item nav-level-2"><a class="nav-link" href="#%E6%9E%84%E5%BB%BA%E9%A1%B9%E7%9B%AE"><span class="nav-number">4.1.</span> <span class="nav-text">构建项目</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E7%9B%91%E5%90%AC%E6%B6%88%E6%81%AF"><span class="nav-number">4.2.</span> <span class="nav-text">监听消息</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E6%B5%8B%E8%AF%95%E6%9C%8D%E5%8A%A1"><span class="nav-number">4.3.</span> <span class="nav-text">测试服务</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#API-%E7%BD%91%E5%85%B3"><span class="nav-number">4.4.</span> <span class="nav-text">API 网关</span></a></li></ol></li></ol></div>
      </div>
      <!--/noindex-->

      <div class="site-overview-wrap sidebar-panel">
        <div class="site-author motion-element" itemprop="author" itemscope itemtype="http://schema.org/Person">
    <img class="site-author-image" itemprop="image" alt="chuangges" src="" data-original="/images/chuang.png">
  <p class="site-author-name" itemprop="name">chuangges</p>
  <div class="site-description" itemprop="description">chuangge's blog.</div>
</div>
<div class="site-state-wrap motion-element">
  <nav class="site-state">
      <div class="site-state-item site-state-posts">
          <a href="/archives/">
        
          <span class="site-state-item-count">39</span>
          <span class="site-state-item-name">日志</span>
        </a>
      </div>
      <div class="site-state-item site-state-categories">
            <a href="/categories/">
          
        <span class="site-state-item-count">7</span>
        <span class="site-state-item-name">分类</span></a>
      </div>
      <div class="site-state-item site-state-tags">
            <a href="/tags/">
          
        <span class="site-state-item-count">7</span>
        <span class="site-state-item-name">标签</span></a>
      </div>
  </nav>
</div>
  <div class="links-of-author motion-element">
      <span class="links-of-author-item">
        <a href="https://github.com/chuangges" title="GitHub → https:&#x2F;&#x2F;github.com&#x2F;chuangges" rel="external nofollow noopener noreferrer" target="_blank"><i class="fab fa-github fa-fw"></i>GitHub</a>
      </span>
      <span class="links-of-author-item">
        <a href="https://stackoverflow.com/yourname" title="StackOverflow → https:&#x2F;&#x2F;stackoverflow.com&#x2F;yourname" rel="external nofollow noopener noreferrer" target="_blank"><i class="fab fa-stack-overflow fa-fw"></i>StackOverflow</a>
      </span>
  </div>


  <div class="links-of-blogroll motion-element">
    <div class="links-of-blogroll-title"><i class="fa fa-link fa-fw"></i>
      友情链接
    </div>
    <ul class="links-of-blogroll-list">
        <li class="links-of-blogroll-item">
          <a href="https://juejin.im/" title="https:&#x2F;&#x2F;juejin.im&#x2F;" rel="external nofollow noopener noreferrer" target="_blank">掘金</a>
        </li>
        <li class="links-of-blogroll-item">
          <a href="https://www.nowcoder.com/" title="https:&#x2F;&#x2F;www.nowcoder.com&#x2F;" rel="external nofollow noopener noreferrer" target="_blank">牛客网</a>
        </li>
        <li class="links-of-blogroll-item">
          <a href="https://www.docschina.org/" title="https:&#x2F;&#x2F;www.docschina.org&#x2F;" rel="external nofollow noopener noreferrer" target="_blank">印记中文</a>
        </li>
        <li class="links-of-blogroll-item">
          <a href="http://tool.lu/" title="http:&#x2F;&#x2F;tool.lu&#x2F;" rel="external nofollow noopener noreferrer" target="_blank">在线工具</a>
        </li>
        <li class="links-of-blogroll-item">
          <a href="https://tinypng.com/" title="https:&#x2F;&#x2F;tinypng.com&#x2F;" rel="external nofollow noopener noreferrer" target="_blank">图片压缩</a>
        </li>
        <li class="links-of-blogroll-item">
          <a href="https://bigjpg.com/" title="https:&#x2F;&#x2F;bigjpg.com&#x2F;" rel="external nofollow noopener noreferrer" target="_blank">图片放大</a>
        </li>
        <li class="links-of-blogroll-item">
          <a href="https://tool.chinaz.com/" title="https:&#x2F;&#x2F;tool.chinaz.com&#x2F;" rel="external nofollow noopener noreferrer" target="_blank">站长工具</a>
        </li>
        <li class="links-of-blogroll-item">
          <a href="https://learn-anything.xyz/" title="https:&#x2F;&#x2F;learn-anything.xyz&#x2F;" rel="external nofollow noopener noreferrer" target="_blank">Learn-Anything</a>
        </li>
        <li class="links-of-blogroll-item">
          <a href="https://stackoverflow.com/" title="https:&#x2F;&#x2F;stackoverflow.com&#x2F;" rel="external nofollow noopener noreferrer" target="_blank">Stack-Overflow</a>
        </li>
    </ul>
  </div>

      </div>

    </div>
  </aside>
  <div id="sidebar-dimmer"></div>


      </div>
    </main>

    <footer class="footer">
      <div class="footer-inner">
        

        

<div class="copyright">
  
  &copy; 
  <span itemprop="copyrightYear">2024</span>
  <span class="with-love">
    <i class="fa fa-heart"></i>
  </span>
  <span class="author" itemprop="copyrightHolder">chuangge</span>
    <span class="post-meta-divider">|</span>
    <span class="post-meta-item-icon">
      <i class="fa fa-chart-area"></i>
    </span>
    <span title="站点总字数">1m</span>
    <span class="post-meta-divider">|</span>
    <span class="post-meta-item-icon">
      <i class="fa fa-coffee"></i>
    </span>
    <span title="站点阅读时长">15:14</span>
</div>
  <div class="powered-by">由 <a href="https://hexo.io/" class="theme-link" rel="external nofollow noopener noreferrer" target="_blank">Hexo</a> & <a href="https://pisces.theme-next.org/" class="theme-link" rel="external nofollow noopener noreferrer" target="_blank">NexT.Pisces</a> 强力驱动
  </div>

        








      </div>
    </footer>
  </div>

  
  <script src="/lib/anime.min.js"></script>
  <script src="/lib/velocity/velocity.min.js"></script>
  <script src="/lib/velocity/velocity.ui.min.js"></script>

<script src="/js/utils.js"></script>

<script src="/js/motion.js"></script>


<script src="/js/schemes/pisces.js"></script>


<script src="/js/next-boot.js"></script>




  















  

  

<script src="/live2dw/lib/L2Dwidget.min.js?094cbace49a39548bed64abff5988b05"></script><script>L2Dwidget.init({"pluginRootPath":"live2dw/","pluginJsPath":"lib/","pluginModelPath":"assets/","model":{"jsonPath":"/live2dw/assets/tororo.model.json"},"display":{"position":"left","width":180,"height":360},"mobile":{"show":false},"react":{"opacityDefault":1,"opacityOnHover":1},"log":false,"tagMode":false});</script>
        <style>
            [bg-lazy] {
                background-image: none !important;
                background-color: #eee !important;
            }
        </style>
        <script>
            window.imageLazyLoadSetting = {
                isSPA: false,
                preloadRatio: 1,
                processImages: null,
            };
        </script><script>window.addEventListener("load",function(){var t=/\.(gif|jpg|jpeg|tiff|png)$/i,r=/^data:image\/[a-z]+;base64,/;Array.prototype.slice.call(document.querySelectorAll("img[data-original]")).forEach(function(a){var e=a.parentNode;"A"===e.tagName&&(e.href.match(t)||e.href.match(r))&&(e.href=a.dataset.original)})});</script><script>!function(e){e.imageLazyLoadSetting.processImages=t;var n=e.imageLazyLoadSetting.isSPA,i=e.imageLazyLoadSetting.preloadRatio||1,r=o();function o(){var t=Array.prototype.slice.call(document.querySelectorAll("img[data-original]")),e=Array.prototype.slice.call(document.querySelectorAll("[bg-lazy]"));return t.concat(e)}function t(){n&&(r=o());for(var t,a=0;a<r.length;a++)0<=(t=(t=r[a]).getBoundingClientRect()).bottom&&0<=t.left&&t.top<=(e.innerHeight*i||document.documentElement.clientHeight*i)&&function(){var t,e,n,i,o=r[a];e=function(){r=r.filter(function(t){return o!==t})},(t=o).hasAttribute("bg-lazy")?(t.removeAttribute("bg-lazy"),e&&e()):(n=new Image,i=t.getAttribute("data-original"),n.onload=function(){t.src=i,t.removeAttribute("data-original"),e&&e()},t.src!==i&&(n.src=i))}()}function a(){clearTimeout(t.tId),t.tId=setTimeout(t,500)}t(),document.addEventListener("scroll",a),e.addEventListener("resize",a),e.addEventListener("orientationchange",a)}(this);</script></body>
</html>
